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

new(**options, &block) click to toggle source

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

Calls superclass method
# File lib/dry/schema/dsl.rb, line 100
def self.new(**options, &block)
  dsl = super
  dsl.instance_eval(&block) if block
  dsl
end

Public Instance Methods

[](name) click to toggle source

Return a macro with the provided name

@param [Symbol] name

@return [Macros::Core]

@api public

# File lib/dry/schema/dsl.rb, line 132
def [](name)
  macros.detect { |macro| macro.name.equal?(name) }
end
after(key, &block) click to toggle source

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 275
def after(key, &block)
  steps.after(key, &block)
  self
end
array() click to toggle source

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 246
def array
  -> member_type { type_registry["array"].of(resolve_type(member_type)) }
end
before(key, &block) click to toggle source

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 260
def before(key, &block)
  steps.before(key, &block)
  self
end
call() click to toggle source

Build a processor based on DSL's definitions

@return [Processor, Params, JSON]

@api private

# File lib/dry/schema/dsl.rb, line 203
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
configure(&block) click to toggle source

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 120
def configure(&block)
  config.configure(&block)
  self
end
custom_type?(name) click to toggle source

Check if a custom type was set under provided key name

@return [Bool]

@api private

# File lib/dry/schema/dsl.rb, line 336
def custom_type?(name)
  !types[name].meta[:default].equal?(true)
end
filter_rules?() click to toggle source

Check if any filter rules were defined

@api private

# File lib/dry/schema/dsl.rb, line 373
def filter_rules?
  if instance_variable_defined?("@filter_schema_dsl") && !filter_schema_dsl.macros.empty?
    return true
  end

  parents.any?(&:filter_rules?)
end
filter_schema() click to toggle source

@api private

# File lib/dry/schema/dsl.rb, line 357
def filter_schema
  filter_schema_dsl.call
end
filter_schema_dsl() click to toggle source

Build an input schema DSL used by `filter` API

@see Macros::Value#filter

@api private

# File lib/dry/schema/dsl.rb, line 366
def filter_schema_dsl
  @filter_schema_dsl ||= new(parent: parent_filter_schemas)
end
key(name, macro:, &block) click to toggle source

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 181
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(other) click to toggle source

Merge with another dsl

@return [DSL]

@api private

# File lib/dry/schema/dsl.rb, line 222
def merge(other)
  new(
    parent: parents + other.parents,
    macros: macros + other.macros,
    types: types.merge(other.types),
    steps: steps.merge(other.steps)
  )
end
merge_types(op_class, lhs, rhs) click to toggle source

@api private

# File lib/dry/schema/dsl.rb, line 389
def merge_types(op_class, lhs, rhs)
  types_merger.(op_class, lhs, rhs)
end
new(**options, &block) click to toggle source

Return a new DSL instance using the same processor type

@return [Dry::Types::Safe]

@api private

# File lib/dry/schema/dsl.rb, line 312
def new(**options, &block)
  self.class.new(**options, processor_type: processor_type, config: config, &block)
end
optional(name, &block) click to toggle source

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 168
def optional(name, &block)
  key(name, macro: Macros::Optional, &block)
end
parent() click to toggle source

The parent (last from parents) which is used for copying non mergeable configuration

@return DSL

@api public

# File lib/dry/schema/dsl.rb, line 285
def parent
  @parent ||= parents.last
end
required(name, &block) click to toggle source

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 152
def required(name, &block)
  key(name, macro: Macros::Required, &block)
end
resolve_type(spec) click to toggle source

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 347
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_type(name, spec) click to toggle source

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 324
def set_type(name, spec)
  type = resolve_type(spec)
  meta = {required: true, maybe: type.optional?}

  @types[name] = type.meta(meta)
end
strict_type_schema() click to toggle source

Return type schema used when composing subschemas

@return [Dry::Types::Schema]

@api private

# File lib/dry/schema/dsl.rb, line 303
def strict_type_schema
  type_registry["hash"].schema(types)
end
to_rule() click to toggle source

Cast this DSL into a rule object

@return [RuleApplier]

# File lib/dry/schema/dsl.rb, line 234
def to_rule
  call.to_rule
end
type_schema() click to toggle source

Return type schema used by the value coercer

@return [Dry::Types::Lax]

@api private

# File lib/dry/schema/dsl.rb, line 294
def type_schema
  strict_type_schema.lax
end
types() click to toggle source

This DSL's type map merged with any parent type maps

@api private

# File lib/dry/schema/dsl.rb, line 384
def types
  [*parents.map(&:types), @types].reduce(:merge)
end

Protected Instance Methods

key_map(types = self.types) click to toggle source

Build a key map from defined types

@api protected

# File lib/dry/schema/dsl.rb, line 416
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
rule_applier() click to toggle source

Build a rule applier

@return [RuleApplier]

@api protected

# File lib/dry/schema/dsl.rb, line 400
def rule_applier
  RuleApplier.new(rules, config: config.finalize!)
end
rules() click to toggle source

Build rules from defined macros

@see rule_applier

@api protected

# File lib/dry/schema/dsl.rb, line 409
def rules
  parent_rules.merge(macros.to_h { [_1.name, _1.to_rule] }.compact)
end

Private Instance Methods

default_config() click to toggle source

@api private

# File lib/dry/schema/dsl.rb, line 509
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
key_coercer() click to toggle source

Build a key coercer

@return [KeyCoercer]

@api private

# File lib/dry/schema/dsl.rb, line 448
def key_coercer
  KeyCoercer.symbolized(key_map)
end
key_map_type() click to toggle source

Return key map type configured by the processor type

@api private

# File lib/dry/schema/dsl.rb, line 474
def key_map_type
  processor_type.config.key_map_type
end
key_spec(name, type) click to toggle source

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 483
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
key_validator() click to toggle source

Build a key validator

@return [KeyValidator]

@api private

# File lib/dry/schema/dsl.rb, line 439
def key_validator
  KeyValidator.new(key_map: key_map)
end
parent_filter_schemas() click to toggle source

@api private

# File lib/dry/schema/dsl.rb, line 430
def parent_filter_schemas
  parents.select(&:filter_rules?).map(&:filter_schema)
end
parent_key_map() click to toggle source

@api private

# File lib/dry/schema/dsl.rb, line 504
def parent_key_map
  parents.reduce([]) { |key_map, parent| parent.key_map + key_map }
end
parent_rules() click to toggle source

@api private

# File lib/dry/schema/dsl.rb, line 499
def parent_rules
  parents.reduce({}) { |rules, parent| rules.merge(parent.rules) }
end
type_registry() click to toggle source

Return type registry configured by the processor type

@api private

# File lib/dry/schema/dsl.rb, line 464
def type_registry
  @type_registry ||= TypeRegistry.new(
    config.types,
    processor_type.config.type_registry_namespace
  )
end
types_merger() click to toggle source
# File lib/dry/schema/dsl.rb, line 520
def types_merger
  @types_merger ||= TypesMerger.new(type_registry)
end
value_coercer() click to toggle source

Build a value coercer

@return [ValueCoercer]

@api private

# File lib/dry/schema/dsl.rb, line 457
def value_coercer
  ValueCoercer.new(type_schema)
end