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 96
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 128
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 270
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 241
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 255
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 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
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 116
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 324
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 361
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 345
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 354
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 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(other) click to toggle source

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
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 300
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 164
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 280
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 148
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 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_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 312
def set_type(name, spec)
  type = resolve_type(spec)
  meta = {required: false, maybe: type.optional?}

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

Cast this DSL into a rule object

@return [RuleApplier]

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

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

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

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

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

default_config() click to toggle source

@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
key_coercer() click to toggle source

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

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

@api private

# File lib/dry/schema/dsl.rb, line 406
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 480
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 475
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 440
def type_registry
  @type_registry ||= TypeRegistry.new(
    config.types,
    processor_type.config.type_registry_namespace
  )
end
value_coercer() click to toggle source

Build a value coercer

@return [ValueCoercer]

@api private

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