# frozen_string_literal: truerequire"dry/logic/operators"require"dry/schema/macros/core"require"dry/schema/predicate_inferrer"require"dry/schema/primitive_inferrer"moduleDrymoduleSchemamoduleMacros# Macro specialization used within the DSL## @api publicclassDSL<CoreincludeDry::Logic::Operatorsundef:eql?undef:nil?# @!attribute [r] chain# Indicate if the macro should append its rules to the provided trace# @return [Boolean]# @api privateoption:chain,default: ->{true}# @!attribute [r] predicate_inferrer# PredicateInferrer is used to infer predicate type-check from a type spec# @return [PredicateInferrer]# @api privateoption:predicate_inferrer,default: proc{PredicateInferrer.new(compiler.predicates)}# @!attribute [r] primitive_inferrer# PrimitiveInferrer used to get a list of primitive classes from configured type# @return [PrimitiveInferrer]# @api privateoption:primitive_inferrer,default: proc{PrimitiveInferrer.new}# @overload value(*predicates, **predicate_opts)# Set predicates without and with arguments## @param [Array<Symbol>] predicates# @param [Hash] predicate_opts## @example with a predicate# required(:name).value(:filled?)## @example with a predicate with arguments# required(:name).value(min_size?: 2)## @example with a predicate with and without arguments# required(:name).value(:filled?, min_size?: 2)## @example with a block# required(:name).value { filled? & min_size?(2) }## @return [Macros::Core]## @api publicdefvalue(*predicates,&block)append_macro(Macros::Value)do|macro|macro.call(*predicates,&block)endendruby2_keywords:valueifrespond_to?(:ruby2_keywords,true)# Prepends `:filled?` predicate## @example with a type spec# required(:name).filled(:string)## @example with a type spec and a predicate# required(:name).filled(:string, format?: /\w+/)## @return [Macros::Core]## @api publicdeffilled(*args,&block)append_macro(Macros::Filled)do|macro|macro.call(*args,&block)endendruby2_keywords:filledifrespond_to?(:ruby2_keywords,true)# Specify a nested hash without enforced `hash?` type-check## This is a simpler building block than `hash` macro, use it# when you want to provide `hash?` type-check with other rules# manually.## @example# required(:tags).value(:hash, min_size?: 1).schema do# required(:name).value(:string)# end## @return [Macros::Core]## @api publicdefschema(*args,&block)append_macro(Macros::Schema)do|macro|macro.call(*args,&block)endendruby2_keywords:schemaifrespond_to?(:ruby2_keywords,true)# Specify a nested hash with enforced `hash?` type-check## @example# required(:tags).hash do# required(:name).value(:string)# end## @api publicdefhash(*args,&block)append_macro(Macros::Hash)do|macro|macro.call(*args,&block)endendruby2_keywords:hashifrespond_to?(:ruby2_keywords,true)# Specify predicates that should be applied to each element of an array## This is a simpler building block than `array` macro, use it# when you want to provide `array?` type-check with other rules# manually.## @example a list of strings# required(:tags).value(:array, min_size?: 2).each(:str?)## @example a list of hashes# required(:tags).value(:array, min_size?: 2).each(:hash) do# required(:name).filled(:string)# end## @return [Macros::Core]## @api publicdefeach(*args,&block)append_macro(Macros::Each)do|macro|macro.value(*args,&block)endendruby2_keywords:eachifrespond_to?(:ruby2_keywords,true)# Like `each` but sets `array?` type-check## @example a list of strings# required(:tags).array(:str?)## @example a list of hashes# required(:tags).array(:hash) do# required(:name).filled(:string)# end## @return [Macros::Core]## @api publicdefarray(*args,&block)append_macro(Macros::Array)do|macro|macro.value(*args,&block)endendruby2_keywords:arrayifrespond_to?(:ruby2_keywords,true)# Set type spec## @example# required(:name).type(:string).value(min_size?: 2)## @param [Symbol, Array, Dry::Types::Type] spec## @return [Macros::Key]## @api publicdeftype(spec)schema_dsl.set_type(name,spec)selfend# @api privatedefcustom_type?schema_dsl.custom_type?(name)endprivate# @api privatedefappend_macro(macro_type)macro=macro_type.new(schema_dsl: schema_dsl,name: name)yield(macro)ifchaintrace<<macroselfelsemacroendend# @api privatedefextract_type_spec(*args,nullable: false,set_type: true)type_spec=args[0]unlessschema_or_predicate?(args[0])predicates=Array(type_spec?args[1..-1]:args)type_rule=niliftype_specresolved_type=resolve_type(type_spec,nullable)iftype_spec.is_a?(::Array)type_rule=type_spec.map{|ts|new(chain: false).value(ts)}.reduce(:|)elsetype_predicates=predicate_inferrer[resolved_type]predicates.replace(type_predicates+predicates)unlesstype_predicates.empty?returnselfifpredicates.empty?endendtype(resolved_type)ifset_type&&resolved_typeiftype_ruleyield(*predicates,type_spec: nil,type_rule: type_rule)elseyield(*predicates,type_spec: type_spec,type_rule: nil)endend# @api privatedefresolve_type(type_spec,nullable)resolved=schema_dsl.resolve_type(type_spec)iftype_spec.is_a?(::Array)||!nullable||resolved.optional?resolvedelseschema_dsl.resolve_type([:nil,resolved])endend# @api privatedefschema_or_predicate?(arg)arg.is_a?(Dry::Schema::Processor)||arg.is_a?(Symbol)&&arg.to_s.end_with?(QUESTION_MARK)endendendendend