class Dry::Types::Schema
The built-in Hash
type can be defined in terms of keys and associated types its values can contain. Such definitions are named {Schema}s and defined as lists of {Key} types.
{Schema} evaluates default values for keys missing in input hash
@see Dry::Types::Default#evaluate
@see Dry::Types::Default::Callable#evaluate
{Schema} implements Enumerable using its keys as collection.
@api public
Schema
is a hash with explicit member types defined
@api public
Constants
- NO_TRANSFORM
- SYMBOLIZE_KEY
Attributes
@return [Array]
@return [Hash[Symbol, Dry::Types::Schema::Key
]]
@return [#call]
Public Class Methods
@param [Class] _primitive @param [Hash] options
@option options [Array] :keys @option options [String] :key_transform_fn
@api private
# File lib/dry/types/schema.rb, line 41 def initialize(_primitive, **options) @keys = options.fetch(:keys) @name_key_map = keys.each_with_object({}) do |key, idx| idx[key.name] = key end key_fn = options.fetch(:key_transform_fn, NO_TRANSFORM) @transform_key = Dry::Types::FnContainer[key_fn] super end
Public Instance Methods
@param [Hash] hash
@option options [Boolean] :skip_missing If true don’t raise error if on missing keys @option options [Boolean] :resolve_defaults If false default value
won't be evaluated for missing key
@return [Hash{Symbol => Object}]
@api public
# File lib/dry/types/schema.rb, line 80 def apply(hash, options = EMPTY_HASH) call_unsafe(hash, options) end
@param [Hash] hash
@return [Hash{Symbol => Object}]
@api private
# File lib/dry/types/schema.rb, line 68 def call_safe(hash, options = EMPTY_HASH) resolve_safe(coerce(hash) { return yield }, options) { return yield } end
@param [Hash] hash
@return [Hash{Symbol => Object}]
@api private
# File lib/dry/types/schema.rb, line 59 def call_unsafe(hash, options = EMPTY_HASH) resolve_unsafe(coerce(hash), options) end
Empty schema with the same options
@return [Schema]
@api public
# File lib/dry/types/schema.rb, line 298 def clear with(keys: EMPTY_ARRAY) end
@return [Boolean]
@api public
# File lib/dry/types/schema.rb, line 270 def constrained? true end
Iterate over each key type
@return [Array<Dry::Types::Schema::Key>,Enumerator]
@api public
# File lib/dry/types/schema.rb, line 229 def each(&block) keys.each(&block) end
Fetch key type by a key name
Behaves as ::Hash#fetch
@overload key(name, fallback = Undefined)
@param [Symbol] name Key name @param [Object] fallback Optional fallback, returned if key is missing @return [Dry::Types::Schema::Key,Object] key type or fallback if key is not in schema
@overload key(name, &block)
@param [Symbol] name Key name @param [Proc] block Fallback block, runs if key is missing @return [Dry::Types::Schema::Key,Object] key type or block value if key is not in schema
@api public
# File lib/dry/types/schema.rb, line 259 def key(name, fallback = Undefined, &block) if Undefined.equal?(fallback) name_key_map.fetch(name, &block) else name_key_map.fetch(name, fallback) end end
Whether the schema has the given key
@param [Symbol] name Key
name
@return [Boolean]
@api public
# File lib/dry/types/schema.rb, line 240 def key?(name) name_key_map.key?(name) end
@return [Lax]
@api public
# File lib/dry/types/schema.rb, line 277 def lax Lax.new(schema(keys.map(&:lax))) end
Merge given schema keys into current schema
A new instance is returned.
@param other [Schema] schema @return [Schema]
@api public
# File lib/dry/types/schema.rb, line 289 def merge(other) schema(other.keys) end
@overload schema(type_map, meta = EMPTY_HASH)
@param [{Symbol => Dry::Types::Nominal}] type_map @param [Hash] meta @return [Dry::Types::Schema]
@overload schema(keys)
@param [Array<Dry::Types::Schema::Key>] key List of schema keys @param [Hash] meta @return [Dry::Types::Schema]
@api public
# File lib/dry/types/schema.rb, line 213 def schema(keys_or_map) if keys_or_map.is_a?(::Array) new_keys = keys_or_map else new_keys = build_keys(keys_or_map) end keys = merge_keys(self.keys, new_keys) Schema.new(primitive, **options, keys: keys, meta: meta) end
Make the schema intolerant to unknown keys
@return [Schema]
@api public
# File lib/dry/types/schema.rb, line 172 def strict(strict = true) # rubocop:disable Style/OptionalBooleanParameter with(strict: strict) end
Whether the schema rejects unknown keys
@return [Boolean]
@api public
# File lib/dry/types/schema.rb, line 163 def strict? options.fetch(:strict, false) end
@param meta [Boolean] Whether to dump the meta to the AST
@return [Array] An AST representation
@api public
# File lib/dry/types/schema.rb, line 149 def to_ast(meta: true) [ :schema, [keys.map { |key| key.to_ast(meta: meta) }, options.slice(:key_transform_fn, :type_transform_fn, :strict), meta ? self.meta : EMPTY_HASH] ] end
Whether the schema transforms input keys
@return [Boolean]
@api public
# File lib/dry/types/schema.rb, line 198 def transform_keys? !options[:key_transform_fn].nil? end
@param input [Hash] hash
@yieldparam [Failure] failure @yieldreturn [Result]
@return [Logic::Result] @return [Object] if coercion fails and a block is given
@api public rubocop:disable Metrics/AbcSize rubocop:disable Metrics/PerceivedComplexity
# File lib/dry/types/schema.rb, line 95 def try(input) if primitive?(input) success = true output = {} result = {} input.each do |key, value| k = @transform_key.(key) type = @name_key_map[k] if type key_result = type.try(value) result[k] = key_result output[k] = key_result.input success &&= key_result.success? elsif strict? success = false end end if output.size < keys.size resolve_missing_keys(output, options) do success = false end end success &&= primitive?(output) if success failure = nil else error = CoercionError.new("#{input} doesn't conform schema", meta: result) failure = failure(output, error) end else failure = failure(input, CoercionError.new("#{input} must be a hash")) end if failure.nil? success(output) elsif block_given? yield(failure) else failure end end
Inject a key transformation function
@param [#call,nil] proc @param [#call,nil] block
@return [Schema]
@api public
# File lib/dry/types/schema.rb, line 184 def with_key_transform(proc = nil, &block) fn = proc || block raise ArgumentError, "a block or callable argument is required" if fn.nil? handle = Dry::Types::FnContainer.register(fn) with(key_transform_fn: handle) end
Private Instance Methods
@param [Array<Dry::Types::Schema::Keys>] keys
@return [Dry::Types::Schema]
@api private
# File lib/dry/types/schema.rb, line 309 def merge_keys(*keys) keys .flatten(1) .each_with_object({}) { |key, merged| merged[key.name] = key } .values end
@return [MissingKeyError]
@api private
# File lib/dry/types/schema.rb, line 405 def missing_key(key) MissingKeyError.new(key) end
Try to add missing keys to the hash
@api private
# File lib/dry/types/schema.rb, line 373 def resolve_missing_keys(hash, options) # rubocop:disable Metrics/PerceivedComplexity skip_missing = options.fetch(:skip_missing, false) resolve_defaults = options.fetch(:resolve_defaults, true) keys.each do |key| next if hash.key?(key.name) if key.default? && resolve_defaults hash[key.name] = key.call_unsafe(Undefined) elsif key.required? && !skip_missing if block_given? return yield else raise missing_key(key.name) end end end end
Validate and coerce a hash. Call a block and halt on any error
@api private
@return [Hash]
# File lib/dry/types/schema.rb, line 351 def resolve_safe(hash, options = EMPTY_HASH, &block) result = {} hash.each do |key, value| k = @transform_key.(key) type = @name_key_map[k] if type result[k] = type.call_safe(value, &block) elsif strict? yield end end resolve_missing_keys(result, options, &block) if result.size < keys.size result end
Validate and coerce a hash. Raise an exception on any error
@api private
@return [Hash]
# File lib/dry/types/schema.rb, line 321 def resolve_unsafe(hash, options = EMPTY_HASH) result = {} hash.each do |key, value| k = @transform_key.(key) type = @name_key_map[k] if type begin result[k] = type.call_unsafe(value) rescue ConstraintError => e raise SchemaError.new(type.name, value, e.result) rescue CoercionError => e raise SchemaError.new(type.name, value, e.message) end elsif strict? raise unexpected_keys(hash.keys) end end resolve_missing_keys(result, options) if result.size < keys.size result end
@param hash_keys [Array<Symbol>]
@return [UnknownKeysError]
@api private
# File lib/dry/types/schema.rb, line 397 def unexpected_keys(hash_keys) extra_keys = hash_keys.map(&transform_key) - name_key_map.keys UnknownKeysError.new(extra_keys) end