class Dry::Schema::DSL
The schema definition DSL
class
The DSL
is exposed by:
- `Schema.define` - `Schema.Params` - `Schema.JSON` - `Schema::Params.define` - use with sub-classes - `Schema::JSON.define` - use with sub-classes
@example class-based definition
class UserSchema < Dry::Schema::Params define do required(:name).filled required(:age).filled(:integer, gt: 18) end end user_schema = UserSchema.new user_schema.(name: 'Jame', age: 21)
@example instance-based definition shortcut
UserSchema = Dry::Schema.Params do required(:name).filled required(:age).filled(:integer, gt: 18) end UserSchema.(name: 'Jame', age: 21)
@api public
Constants
- Types
Public Class Methods
Build a new DSL
object and evaluate provided block
@param [Hash] options @option options [Class] :processor The processor type (`Params`, `JSON` or a custom sub-class) @option options [Compiler] :compiler An instance of a rule compiler (must be compatible with `Schema::Compiler`) (optional) @option options [Array] :parent One or more instances of the parent DSL
(optional) @option options [Config] :config A configuration object (optional)
@see Schema.define
@see Schema
.Params @see Schema
.JSON @see Processor.define
@return [DSL]
@api public
# File lib/dry/schema/dsl.rb, line 96 def self.new(**options, &block) dsl = super dsl.instance_eval(&block) if block dsl end
Public Instance Methods
Return a macro with the provided name
@param [Symbol] name
@return [Macros::Core]
@api public
# File lib/dry/schema/dsl.rb, line 128 def [](name) macros.detect { |macro| macro.name.equal?(name) } end
Method allows steps injection to the processor
@example
after(:rule_applier) do |input| input.compact end
@return [DSL]
@api public
# File lib/dry/schema/dsl.rb, line 270 def after(key, &block) steps.after(key, &block) self end
A shortcut for defining an array type with a member
@example
required(:tags).filled(array[:string])
@return [Dry::Types::Array::Member]
@api public
# File lib/dry/schema/dsl.rb, line 241 def array -> member_type { type_registry["array"].of(resolve_type(member_type)) } end
Method allows steps injection to the processor
@example
before(:rule_applier) do |input| input.compact end
@return [DSL]
@api public
# File lib/dry/schema/dsl.rb, line 255 def before(key, &block) steps.before(key, &block) self end
Build a processor based on DSL's definitions
@return [Processor, Params
, JSON]
@api private
# File lib/dry/schema/dsl.rb, line 198 def call all_steps = parents.map(&:steps) + [steps] result_steps = all_steps.inject { |result, steps| result.merge(steps) } result_steps[:key_validator] = key_validator if config.validate_keys result_steps[:key_coercer] = key_coercer result_steps[:value_coercer] = value_coercer result_steps[:rule_applier] = rule_applier result_steps[:filter_schema] = filter_schema.rule_applier if filter_rules? processor_type.new(schema_dsl: self, steps: result_steps) end
Provide customized configuration for your schema
@example
Dry::Schema.define do configure do |config| config.messages.backend = :i18n end end
@see Config
@return [DSL]
@api public
# File lib/dry/schema/dsl.rb, line 116 def configure(&block) config.configure(&block) self end
Check if a custom type was set under provided key name
@return [Bool]
@api private
# File lib/dry/schema/dsl.rb, line 324 def custom_type?(name) !types[name].meta[:default].equal?(true) end
Check if any filter rules were defined
@api private
# File lib/dry/schema/dsl.rb, line 361 def filter_rules? if instance_variable_defined?("@filter_schema_dsl") && !filter_schema_dsl.macros.empty? return true end parents.any?(&:filter_rules?) end
@api private
# File lib/dry/schema/dsl.rb, line 345 def filter_schema filter_schema_dsl.call end
Build an input schema DSL
used by `filter` API
@see Macros::Value#filter
@api private
# File lib/dry/schema/dsl.rb, line 354 def filter_schema_dsl @filter_schema_dsl ||= new(parent: parent_filter_schemas) end
A generic method for defining keys
@param [Symbol] name The key name @param [Class] macro The macro sub-class (ie `Macros::Required` or any other `Macros::Key` subclass)
@return [Macros::Key]
@api public
# File lib/dry/schema/dsl.rb, line 176 def key(name, macro:, &block) raise ArgumentError, "Key +#{name}+ is not a symbol" unless name.is_a?(::Symbol) set_type(name, Types::Any.meta(default: true)) macro = macro.new( name: name, compiler: compiler, schema_dsl: self, filter_schema_dsl: filter_schema_dsl ) macro.value(&block) if block macros << macro macro end
Merge with another dsl
@return [DSL]
@api private
# File lib/dry/schema/dsl.rb, line 217 def merge(other) new( parent: parents + other.parents, macros: macros + other.macros, types: types.merge(other.types), steps: steps.merge(other.steps) ) end
Return a new DSL
instance using the same processor type
@return [Dry::Types::Safe]
@api private
# File lib/dry/schema/dsl.rb, line 300 def new(**options, &block) self.class.new(**options, processor_type: processor_type, config: config, &block) end
Define an optional key
This works exactly the same as `required` except that if a key is not present rules will not be applied
@see DSL#required
@param [Symbol] name The key name
@return [Macros::Optional]
@api public
# File lib/dry/schema/dsl.rb, line 164 def optional(name, &block) key(name, macro: Macros::Optional, &block) end
The parent (last from parents) which is used for copying non mergeable configuration
@return DSL
@api public
# File lib/dry/schema/dsl.rb, line 280 def parent @parent ||= parents.last end
Define a required key
@example
required(:name).filled required(:age).value(:integer) required(:user_limit).value(:integer, gt?: 0) required(:tags).filled { array? | str? }
@param [Symbol] name The key name
@return [Macros::Required]
@api public
# File lib/dry/schema/dsl.rb, line 148 def required(name, &block) key(name, macro: Macros::Required, &block) end
Resolve type object from the provided spec
@param [Symbol, Array<Symbol>, Dry::Types::Type] spec
@return [Dry::Types::Type]
@api private
# File lib/dry/schema/dsl.rb, line 335 def resolve_type(spec) case spec when ::Dry::Types::Type then spec when ::Array then spec.map { |s| resolve_type(s) }.reduce(:|) else type_registry[spec] end end
Set a type for the given key name
@param [Symbol] name The key name @param [Symbol, Array<Symbol>, Dry::Types::Type] spec The type spec or a type object
@return [Dry::Types::Safe]
@api private
# File lib/dry/schema/dsl.rb, line 312 def set_type(name, spec) type = resolve_type(spec) meta = {required: false, maybe: type.optional?} types[name] = type.meta(meta) end
Cast this DSL
into a rule object
@return [RuleApplier]
# File lib/dry/schema/dsl.rb, line 229 def to_rule call.to_rule end
Return type schema used by the value coercer
@return [Dry::Types::Safe]
@api private
# File lib/dry/schema/dsl.rb, line 289 def type_schema our_schema = type_registry["hash"].schema(types).lax schemas = [*parents.map(&:type_schema), our_schema] schemas.inject { |result, schema| result.schema(schema.to_a) } end
Protected Instance Methods
Build a key map from defined types
@api protected
# File lib/dry/schema/dsl.rb, line 392 def key_map(types = self.types) keys = types.map { |key, type| key_spec(key, type) } km = KeyMap.new(keys) if key_map_type km.public_send(key_map_type) else km end end
Build a rule applier
@return [RuleApplier]
@api protected
# File lib/dry/schema/dsl.rb, line 376 def rule_applier RuleApplier.new(rules, config: config.finalize!) end
Build rules from defined macros
@see rule_applier
@api protected
# File lib/dry/schema/dsl.rb, line 385 def rules parent_rules.merge(macros.map { |m| [m.name, m.to_rule] }.to_h.compact) end
Private Instance Methods
@api private
# File lib/dry/schema/dsl.rb, line 485 def default_config parents.each_cons(2) do |left, right| unless left.config == right.config raise ArgumentError, "Parent configs differ, left=#{left.inspect}, right=#{right.inspect}" end end (parent || Schema).config.dup end
Build a key coercer
@return [KeyCoercer]
@api private
# File lib/dry/schema/dsl.rb, line 424 def key_coercer KeyCoercer.symbolized(key_map + parent_key_map) end
Return key map type configured by the processor type
@api private
# File lib/dry/schema/dsl.rb, line 450 def key_map_type processor_type.config.key_map_type end
Build a key spec needed by the key map
TODO: we need a key-map compiler using Types
AST
@api private
# File lib/dry/schema/dsl.rb, line 459 def key_spec(name, type) if type.respond_to?(:keys) {name => key_map(type.name_key_map)} elsif type.respond_to?(:member) kv = key_spec(name, type.member) kv.equal?(name) ? name : kv.flatten(1) elsif type.meta[:maybe] && type.respond_to?(:right) key_spec(name, type.right) elsif type.respond_to?(:type) key_spec(name, type.type) else name end end
Build a key validator
@return [KeyValidator]
@api private
# File lib/dry/schema/dsl.rb, line 415 def key_validator KeyValidator.new(key_map: key_map + parent_key_map) end
@api private
# File lib/dry/schema/dsl.rb, line 406 def parent_filter_schemas parents.select(&:filter_rules?).map(&:filter_schema) end
@api private
# File lib/dry/schema/dsl.rb, line 480 def parent_key_map parents.reduce([]) { |key_map, parent| parent.key_map + key_map } end
@api private
# File lib/dry/schema/dsl.rb, line 475 def parent_rules parents.reduce({}) { |rules, parent| rules.merge(parent.rules) } end
Return type registry configured by the processor type
@api private
# File lib/dry/schema/dsl.rb, line 440 def type_registry @type_registry ||= TypeRegistry.new( config.types, processor_type.config.type_registry_namespace ) end
Build a value coercer
@return [ValueCoercer]
@api private
# File lib/dry/schema/dsl.rb, line 433 def value_coercer ValueCoercer.new(type_schema) end