dry-schema 1.3.4 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +253 -101
  3. data/LICENSE +1 -1
  4. data/README.md +6 -6
  5. data/config/errors.yml +4 -0
  6. data/dry-schema.gemspec +46 -0
  7. data/lib/dry-schema.rb +1 -1
  8. data/lib/dry/schema.rb +20 -7
  9. data/lib/dry/schema/compiler.rb +4 -4
  10. data/lib/dry/schema/config.rb +15 -6
  11. data/lib/dry/schema/constants.rb +19 -9
  12. data/lib/dry/schema/dsl.rb +144 -38
  13. data/lib/dry/schema/extensions.rb +10 -2
  14. data/lib/dry/schema/extensions/hints.rb +15 -8
  15. data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +2 -2
  16. data/lib/dry/schema/extensions/hints/message_set_methods.rb +0 -47
  17. data/lib/dry/schema/extensions/info.rb +27 -0
  18. data/lib/dry/schema/extensions/info/schema_compiler.rb +105 -0
  19. data/lib/dry/schema/extensions/monads.rb +1 -1
  20. data/lib/dry/schema/extensions/struct.rb +32 -0
  21. data/lib/dry/schema/json.rb +1 -1
  22. data/lib/dry/schema/key.rb +20 -5
  23. data/lib/dry/schema/key_coercer.rb +4 -4
  24. data/lib/dry/schema/key_map.rb +9 -4
  25. data/lib/dry/schema/key_validator.rb +66 -0
  26. data/lib/dry/schema/macros.rb +8 -8
  27. data/lib/dry/schema/macros/array.rb +17 -4
  28. data/lib/dry/schema/macros/core.rb +11 -6
  29. data/lib/dry/schema/macros/dsl.rb +53 -21
  30. data/lib/dry/schema/macros/each.rb +4 -4
  31. data/lib/dry/schema/macros/filled.rb +5 -6
  32. data/lib/dry/schema/macros/hash.rb +21 -3
  33. data/lib/dry/schema/macros/key.rb +10 -10
  34. data/lib/dry/schema/macros/maybe.rb +4 -5
  35. data/lib/dry/schema/macros/optional.rb +1 -1
  36. data/lib/dry/schema/macros/required.rb +1 -1
  37. data/lib/dry/schema/macros/schema.rb +23 -2
  38. data/lib/dry/schema/macros/value.rb +34 -7
  39. data/lib/dry/schema/message.rb +35 -9
  40. data/lib/dry/schema/message/or.rb +18 -39
  41. data/lib/dry/schema/message/or/abstract.rb +28 -0
  42. data/lib/dry/schema/message/or/multi_path.rb +37 -0
  43. data/lib/dry/schema/message/or/single_path.rb +64 -0
  44. data/lib/dry/schema/message_compiler.rb +40 -19
  45. data/lib/dry/schema/message_compiler/visitor_opts.rb +2 -2
  46. data/lib/dry/schema/message_set.rb +26 -37
  47. data/lib/dry/schema/messages.rb +6 -6
  48. data/lib/dry/schema/messages/abstract.rb +79 -66
  49. data/lib/dry/schema/messages/i18n.rb +36 -10
  50. data/lib/dry/schema/messages/namespaced.rb +13 -3
  51. data/lib/dry/schema/messages/template.rb +19 -44
  52. data/lib/dry/schema/messages/yaml.rb +72 -13
  53. data/lib/dry/schema/params.rb +1 -1
  54. data/lib/dry/schema/path.rb +44 -5
  55. data/lib/dry/schema/predicate.rb +2 -2
  56. data/lib/dry/schema/predicate_inferrer.rb +4 -184
  57. data/lib/dry/schema/predicate_registry.rb +3 -24
  58. data/lib/dry/schema/primitive_inferrer.rb +3 -86
  59. data/lib/dry/schema/processor.rb +54 -50
  60. data/lib/dry/schema/processor_steps.rb +139 -0
  61. data/lib/dry/schema/result.rb +52 -5
  62. data/lib/dry/schema/rule_applier.rb +8 -7
  63. data/lib/dry/schema/step.rb +79 -0
  64. data/lib/dry/schema/trace.rb +5 -4
  65. data/lib/dry/schema/type_container.rb +3 -3
  66. data/lib/dry/schema/type_registry.rb +2 -2
  67. data/lib/dry/schema/types.rb +1 -1
  68. data/lib/dry/schema/value_coercer.rb +2 -2
  69. data/lib/dry/schema/version.rb +1 -1
  70. metadata +21 -7
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2019 dry-rb team
3
+ Copyright (c) 2015-2020 dry-rb team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  [gem]: https://rubygems.org/gems/dry-schema
2
- [travis]: https://travis-ci.com/dry-rb/dry-schema
3
- [codeclimate]: https://codeclimate.com/github/dry-rb/dry-schema
2
+ [actions]: https://github.com/dry-rb/dry-schema/actions
3
+ [codacy]: https://www.codacy.com/gh/dry-rb/dry-schema
4
4
  [chat]: https://dry-rb.zulipchat.com
5
5
  [inchpages]: http://inch-ci.org/github/dry-rb/dry-schema
6
6
 
7
7
  # dry-schema [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
8
8
 
9
9
  [![Gem Version](https://badge.fury.io/rb/dry-schema.svg)][gem]
10
- [![Build Status](https://travis-ci.com/dry-rb/dry-schema.svg?branch=master)][travis]
11
- [![Code Climate](https://codeclimate.com/github/dry-rb/dry-schema/badges/gpa.svg)][codeclimate]
12
- [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-schema/badges/coverage.svg)][codeclimate]
10
+ [![CI Status](https://github.com/dry-rb/dry-schema/workflows/ci/badge.svg)][actions]
11
+ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/961f5c776f1d49218b2cede3745e059c)][codacy]
12
+ [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/961f5c776f1d49218b2cede3745e059c)][codacy]
13
13
  [![Inline docs](http://inch-ci.org/github/dry-rb/dry-schema.svg?branch=master)][inchpages]
14
14
 
15
15
  ## Links
@@ -19,7 +19,7 @@
19
19
 
20
20
  ## Supported Ruby versions
21
21
 
22
- This library officially supports following Ruby versions:
22
+ This library officially supports the following Ruby versions:
23
23
 
24
24
  * MRI >= `2.4`
25
25
  * jruby >= `9.2`
@@ -3,6 +3,8 @@ en:
3
3
  or: "or"
4
4
 
5
5
  errors:
6
+ unexpected_key: "is not allowed"
7
+
6
8
  array?: "must be an array"
7
9
 
8
10
  empty?: "must be empty"
@@ -101,5 +103,7 @@ en:
101
103
  default: "must be %{size} bytes long"
102
104
  range: "must be within %{size_left} - %{size_right} bytes long"
103
105
 
106
+ uuid_v4?: "is not a valid UUID"
107
+
104
108
  not:
105
109
  empty?: "cannot be empty"
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ # this file is managed by dry-rb/devtools project
3
+
4
+ lib = File.expand_path('lib', __dir__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require 'dry/schema/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'dry-schema'
10
+ spec.authors = ["Piotr Solnica"]
11
+ spec.email = ["piotr.solnica@gmail.com"]
12
+ spec.license = 'MIT'
13
+ spec.version = Dry::Schema::VERSION.dup
14
+
15
+ spec.summary = "Coercion and validation for data structures"
16
+ spec.description = <<~TEXT
17
+ dry-schema provides a DSL for defining schemas with keys and rules that should be applied to
18
+ values. It supports coercion, input sanitization, custom types and localized error messages
19
+ (with or without I18n gem). It's also used as the schema engine in dry-validation.
20
+ TEXT
21
+ spec.homepage = 'https://dry-rb.org/gems/dry-schema'
22
+ spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-schema.gemspec", "lib/**/*", "config/*.yml"]
23
+ spec.bindir = 'bin'
24
+ spec.executables = []
25
+ spec.require_paths = ['lib']
26
+
27
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
28
+ spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-schema/blob/master/CHANGELOG.md'
29
+ spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-schema'
30
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-schema/issues'
31
+
32
+ spec.required_ruby_version = ">= 2.4.0"
33
+
34
+ # to update dependencies edit project.yml
35
+ spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
36
+ spec.add_runtime_dependency "dry-configurable", "~> 0.8", ">= 0.8.3"
37
+ spec.add_runtime_dependency "dry-core", "~> 0.4"
38
+ spec.add_runtime_dependency "dry-equalizer", "~> 0.2"
39
+ spec.add_runtime_dependency "dry-initializer", "~> 3.0"
40
+ spec.add_runtime_dependency "dry-logic", "~> 1.0"
41
+ spec.add_runtime_dependency "dry-types", "~> 1.4"
42
+
43
+ spec.add_development_dependency "bundler"
44
+ spec.add_development_dependency "rake"
45
+ spec.add_development_dependency "rspec"
46
+ end
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema'
3
+ require "dry/schema"
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core/extensions'
3
+ require "dry/core/extensions"
4
4
 
5
- require 'dry/schema/constants'
6
- require 'dry/schema/dsl'
7
- require 'dry/schema/params'
8
- require 'dry/schema/json'
5
+ require "dry/schema/config"
6
+ require "dry/schema/constants"
7
+ require "dry/schema/dsl"
8
+ require "dry/schema/params"
9
+ require "dry/schema/json"
9
10
 
10
11
  module Dry
11
12
  # Main interface
@@ -14,6 +15,18 @@ module Dry
14
15
  module Schema
15
16
  extend Dry::Core::Extensions
16
17
 
18
+ # Configuration
19
+ #
20
+ # @example
21
+ # Dry::Schema.config.messages.backend = :i18n
22
+ #
23
+ # @return [Config]
24
+ #
25
+ # @api public
26
+ def self.config
27
+ @config ||= Config.new
28
+ end
29
+
17
30
  # Define a schema
18
31
  #
19
32
  # @example
@@ -30,7 +43,7 @@ module Dry
30
43
  #
31
44
  # @api public
32
45
  def self.define(**options, &block)
33
- DSL.new(options, &block).call
46
+ DSL.new(**options, &block).call
34
47
  end
35
48
 
36
49
  # Define a schema suitable for HTTP params
@@ -74,4 +87,4 @@ module Dry
74
87
  end
75
88
  end
76
89
 
77
- require 'dry/schema/extensions'
90
+ require "dry/schema/extensions"
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/rule_compiler'
4
- require 'dry/schema/namespaced_rule'
5
- require 'dry/schema/predicate_registry'
3
+ require "dry/logic/rule_compiler"
4
+ require "dry/schema/namespaced_rule"
5
+ require "dry/schema/predicate_registry"
6
6
 
7
7
  module Dry
8
8
  module Schema
@@ -35,7 +35,7 @@ module Dry
35
35
  # @return [NamespacedRule]
36
36
  #
37
37
  # @api private
38
- def visit_namespace(node, opts = EMPTY_HASH)
38
+ def visit_namespace(node, _opts = EMPTY_HASH)
39
39
  namespace, rest = node
40
40
  NamespacedRule.new(namespace, visit(rest))
41
41
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/equalizer'
4
- require 'dry/configurable'
3
+ require "dry/equalizer"
4
+ require "dry/configurable"
5
5
 
6
- require 'dry/schema/constants'
7
- require 'dry/schema/predicate_registry'
8
- require 'dry/schema/type_container'
6
+ require "dry/schema/constants"
7
+ require "dry/schema/predicate_registry"
8
+ require "dry/schema/type_container"
9
9
 
10
10
  module Dry
11
11
  module Schema
@@ -51,6 +51,15 @@ module Dry
51
51
  setting(:default_locale, nil)
52
52
  end
53
53
 
54
+ # @!method validate_keys
55
+ #
56
+ # On/off switch for key validator
57
+ #
58
+ # @return [Boolean]
59
+ #
60
+ # @api public
61
+ setting(:validate_keys, false)
62
+
54
63
  # @api private
55
64
  def respond_to_missing?(meth, include_private = false)
56
65
  super || config.respond_to?(meth, include_private)
@@ -58,7 +67,7 @@ module Dry
58
67
 
59
68
  # @api private
60
69
  def inspect
61
- "#<#{self.class} #{to_h.map { |k,v| ["#{k}=", v.inspect] }.map(&:join).join(' ')}>"
70
+ "#<#{self.class} #{to_h.map { |k, v| ["#{k}=", v.inspect] }.map(&:join).join(" ")}>"
62
71
  end
63
72
 
64
73
  private
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pathname'
4
- require 'dry/core/constants'
3
+ require "pathname"
4
+ require "dry/core/constants"
5
5
 
6
6
  module Dry
7
7
  # Common constants used across the library
@@ -10,15 +10,24 @@ module Dry
10
10
  module Schema
11
11
  include Core::Constants
12
12
 
13
- LIST_SEPARATOR = ', '
14
- QUESTION_MARK = '?'
15
- DOT = '.'
13
+ LIST_SEPARATOR = ", "
14
+ QUESTION_MARK = "?"
15
+ DOT = "."
16
+
17
+ # core processor steps in the default execution order
18
+ STEPS_IN_ORDER = %i[
19
+ key_validator
20
+ key_coercer
21
+ filter_schema
22
+ value_coercer
23
+ rule_applier
24
+ ].freeze
16
25
 
17
26
  # Path to the default set of localized messages bundled within the gem
18
- DEFAULT_MESSAGES_PATH = Pathname(__dir__).join('../../../config/errors.yml').realpath.freeze
27
+ DEFAULT_MESSAGES_PATH = Pathname(__dir__).join("../../../config/errors.yml").realpath.freeze
19
28
 
20
29
  # Default namespace used for localized messages in YAML files
21
- DEFAULT_MESSAGES_ROOT = 'dry_schema'
30
+ DEFAULT_MESSAGES_ROOT = "dry_schema"
22
31
 
23
32
  # An error raised when DSL is used in an incorrect way
24
33
  InvalidSchemaError = Class.new(StandardError)
@@ -26,10 +35,11 @@ module Dry
26
35
  # An error raised when a localized message cannot be found
27
36
  MissingMessageError = Class.new(StandardError) do
28
37
  # @api private
29
- def initialize(path)
38
+ def initialize(path, paths = [])
30
39
  *rest, rule = path
31
40
  super(<<~STR)
32
- Message template for #{rule.inspect} under #{rest.join(DOT).inspect} was not found
41
+ Message template for #{rule.inspect} under #{rest.join(DOT).inspect} was not found. Searched in:
42
+ #{paths.map { |string| "\"#{string}\"" }.join("\n")}
33
43
  STR
34
44
  end
35
45
  end
@@ -1,18 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/initializer'
4
-
5
- require 'dry/schema/constants'
6
- require 'dry/schema/config'
7
- require 'dry/schema/compiler'
8
- require 'dry/schema/types'
9
- require 'dry/schema/macros'
10
-
11
- require 'dry/schema/processor'
12
- require 'dry/schema/key_map'
13
- require 'dry/schema/key_coercer'
14
- require 'dry/schema/value_coercer'
15
- require 'dry/schema/rule_applier'
3
+ require "dry/initializer"
4
+
5
+ require "dry/schema"
6
+ require "dry/schema/constants"
7
+ require "dry/schema/path"
8
+ require "dry/schema/config"
9
+ require "dry/schema/compiler"
10
+ require "dry/schema/types"
11
+ require "dry/schema/macros"
12
+
13
+ require "dry/schema/processor"
14
+ require "dry/schema/processor_steps"
15
+ require "dry/schema/key_map"
16
+ require "dry/schema/key_coercer"
17
+ require "dry/schema/key_validator"
18
+ require "dry/schema/value_coercer"
19
+ require "dry/schema/rule_applier"
16
20
 
17
21
  module Dry
18
22
  module Schema
@@ -50,8 +54,6 @@ module Dry
50
54
 
51
55
  extend Dry::Initializer
52
56
 
53
- include ::Dry::Equalizer(:options)
54
-
55
57
  # @return [Compiler] The rule compiler object
56
58
  option :compiler, default: -> { Compiler.new }
57
59
 
@@ -64,18 +66,24 @@ module Dry
64
66
  # @return [Compiler] A key=>type map defined within the DSL
65
67
  option :types, default: -> { EMPTY_HASH.dup }
66
68
 
67
- # @return [DSL] An optional parent DSL object that will be used to merge keys and rules
68
- option :parent, optional: true
69
+ # @return [Array] Optional parent DSL objects, that will be used to merge keys and rules
70
+ option :parent, Types::Coercible::Array, default: -> { EMPTY_ARRAY.dup }, as: :parents
69
71
 
70
72
  # @return [Config] Configuration object exposed via `#configure` method
71
- option :config, optional: true, default: proc { parent ? parent.config.dup : Config.new }
73
+ option :config, optional: true, default: proc { default_config }
74
+
75
+ # @return [ProcessorSteps] Steps for the processor
76
+ option :steps, default: proc { ProcessorSteps.new }
77
+
78
+ # @return [Path, Array] Path under which the schema is defined
79
+ option :path, -> *args { Path[*args] if args.any? }, default: proc { EMPTY_ARRAY }
72
80
 
73
81
  # Build a new DSL object and evaluate provided block
74
82
  #
75
83
  # @param [Hash] options
76
84
  # @option options [Class] :processor The processor type (`Params`, `JSON` or a custom sub-class)
77
85
  # @option options [Compiler] :compiler An instance of a rule compiler (must be compatible with `Schema::Compiler`) (optional)
78
- # @option options [DSL] :parent An instance of the parent DSL (optional)
86
+ # @option options [Array[DSL]] :parent One or more instances of the parent DSL (optional)
79
87
  # @option options [Config] :config A configuration object (optional)
80
88
  #
81
89
  # @see Schema.define
@@ -169,7 +177,7 @@ module Dry
169
177
  def key(name, macro:, &block)
170
178
  raise ArgumentError, "Key +#{name}+ is not a symbol" unless name.is_a?(::Symbol)
171
179
 
172
- set_type(name, Types::Any)
180
+ set_type(name, Types::Any.meta(default: true))
173
181
 
174
182
  macro = macro.new(
175
183
  name: name,
@@ -189,11 +197,31 @@ module Dry
189
197
  #
190
198
  # @api private
191
199
  def call
192
- steps = [key_coercer]
193
- steps << filter_schema.rule_applier if filter_rules?
194
- steps << value_coercer << rule_applier
200
+ all_steps = parents.map(&:steps) + [steps]
201
+
202
+ result_steps = all_steps.inject { |result, steps| result.merge(steps) }
203
+
204
+ result_steps[:key_validator] = key_validator if config.validate_keys
205
+ result_steps[:key_coercer] = key_coercer
206
+ result_steps[:value_coercer] = value_coercer
207
+ result_steps[:rule_applier] = rule_applier
208
+ result_steps[:filter_schema] = filter_schema.rule_applier if filter_rules?
195
209
 
196
- processor_type.new(schema_dsl: self, steps: steps)
210
+ processor_type.new(schema_dsl: self, steps: result_steps)
211
+ end
212
+
213
+ # Merge with another dsl
214
+ #
215
+ # @return [DSL]
216
+ #
217
+ # @api private
218
+ def merge(other)
219
+ new(
220
+ parent: parents + other.parents,
221
+ macros: macros + other.macros,
222
+ types: types.merge(other.types),
223
+ steps: steps.merge(other.steps)
224
+ )
197
225
  end
198
226
 
199
227
  # Cast this DSL into a rule object
@@ -212,7 +240,46 @@ module Dry
212
240
  #
213
241
  # @api public
214
242
  def array
215
- -> member_type { type_registry['array'].of(resolve_type(member_type)) }
243
+ -> member_type { type_registry["array"].of(resolve_type(member_type)) }
244
+ end
245
+
246
+ # Method allows steps injection to the processor
247
+ #
248
+ # @example
249
+ # before(:rule_applier) do |input|
250
+ # input.compact
251
+ # end
252
+ #
253
+ # @return [DSL]
254
+ #
255
+ # @api public
256
+ def before(key, &block)
257
+ steps.before(key, &block)
258
+ self
259
+ end
260
+
261
+ # Method allows steps injection to the processor
262
+ #
263
+ # @example
264
+ # after(:rule_applier) do |input|
265
+ # input.compact
266
+ # end
267
+ #
268
+ # @return [DSL]
269
+ #
270
+ # @api public
271
+ def after(key, &block)
272
+ steps.after(key, &block)
273
+ self
274
+ end
275
+
276
+ # The parent (last from parents) which is used for copying non mergeable configuration
277
+ #
278
+ # @return DSL
279
+ #
280
+ # @api public
281
+ def parent
282
+ @parent ||= parents.last
216
283
  end
217
284
 
218
285
  # Return type schema used by the value coercer
@@ -221,8 +288,9 @@ module Dry
221
288
  #
222
289
  # @api private
223
290
  def type_schema
224
- schema = type_registry['hash'].schema(types).lax
225
- parent ? parent.type_schema.schema(schema.to_a) : schema
291
+ our_schema = type_registry["hash"].schema(types).lax
292
+ schemas = [*parents.map(&:type_schema), our_schema]
293
+ schemas.inject { |result, schema| result.schema(schema.to_a) }
226
294
  end
227
295
 
228
296
  # Return a new DSL instance using the same processor type
@@ -230,8 +298,8 @@ module Dry
230
298
  # @return [Dry::Types::Safe]
231
299
  #
232
300
  # @api private
233
- def new(options = EMPTY_HASH, &block)
234
- self.class.new(options.merge(processor_type: processor_type, config: config), &block)
301
+ def new(**options, &block)
302
+ self.class.new(**options, processor_type: processor_type, config: config, &block)
235
303
  end
236
304
 
237
305
  # Set a type for the given key name
@@ -244,11 +312,20 @@ module Dry
244
312
  # @api private
245
313
  def set_type(name, spec)
246
314
  type = resolve_type(spec)
247
- meta = { required: false, maybe: type.optional? }
315
+ meta = {required: false, maybe: type.optional?}
248
316
 
249
317
  types[name] = type.meta(meta)
250
318
  end
251
319
 
320
+ # Check if a custom type was set under provided key name
321
+ #
322
+ # @return [Bool]
323
+ #
324
+ # @api private
325
+ def custom_type?(name)
326
+ !types[name].meta[:default].equal?(true)
327
+ end
328
+
252
329
  # Resolve type object from the provided spec
253
330
  #
254
331
  # @param [Symbol, Array<Symbol>, Dry::Types::Type] spec
@@ -276,14 +353,18 @@ module Dry
276
353
  #
277
354
  # @api private
278
355
  def filter_schema_dsl
279
- @filter_schema_dsl ||= new(parent: parent_filter_schema)
356
+ @filter_schema_dsl ||= new(parent: parent_filter_schemas)
280
357
  end
281
358
 
282
359
  # Check if any filter rules were defined
283
360
  #
284
361
  # @api private
285
362
  def filter_rules?
286
- (instance_variable_defined?('@filter_schema_dsl') && !filter_schema_dsl.macros.empty?) || parent&.filter_rules?
363
+ if instance_variable_defined?("@filter_schema_dsl") && !filter_schema_dsl.macros.empty?
364
+ return true
365
+ end
366
+
367
+ parents.any?(&:filter_rules?)
287
368
  end
288
369
 
289
370
  protected
@@ -323,10 +404,17 @@ module Dry
323
404
  private
324
405
 
325
406
  # @api private
326
- def parent_filter_schema
327
- return unless parent
407
+ def parent_filter_schemas
408
+ parents.select(&:filter_rules?).map(&:filter_schema)
409
+ end
328
410
 
329
- parent.filter_schema if parent.filter_rules?
411
+ # Build a key validator
412
+ #
413
+ # @return [KeyValidator]
414
+ #
415
+ # @api private
416
+ def key_validator
417
+ KeyValidator.new(key_map: key_map + parent_key_map)
330
418
  end
331
419
 
332
420
  # Build a key coercer
@@ -366,13 +454,19 @@ module Dry
366
454
 
367
455
  # Build a key spec needed by the key map
368
456
  #
457
+ # TODO: we need a key-map compiler using Types AST
458
+ #
369
459
  # @api private
370
460
  def key_spec(name, type)
371
461
  if type.respond_to?(:keys)
372
- { name => key_map(type.name_key_map) }
462
+ {name => key_map(type.name_key_map)}
373
463
  elsif type.respond_to?(:member)
374
464
  kv = key_spec(name, type.member)
375
465
  kv.equal?(name) ? name : kv.flatten(1)
466
+ elsif type.meta[:maybe] && type.respond_to?(:right)
467
+ key_spec(name, type.right)
468
+ elsif type.respond_to?(:type)
469
+ key_spec(name, type.type)
376
470
  else
377
471
  name
378
472
  end
@@ -380,12 +474,24 @@ module Dry
380
474
 
381
475
  # @api private
382
476
  def parent_rules
383
- parent&.rules || EMPTY_HASH
477
+ parents.reduce({}) { |rules, parent| rules.merge(parent.rules) }
384
478
  end
385
479
 
386
480
  # @api private
387
481
  def parent_key_map
388
- parent&.key_map || EMPTY_ARRAY
482
+ parents.reduce([]) { |key_map, parent| parent.key_map + key_map }
483
+ end
484
+
485
+ # @api private
486
+ def default_config
487
+ parents.each_cons(2) do |left, right|
488
+ unless left.config == right.config
489
+ raise ArgumentError,
490
+ "Parent configs differ, left=#{left.inspect}, right=#{right.inspect}"
491
+ end
492
+ end
493
+
494
+ (parent || Schema).config.dup
389
495
  end
390
496
  end
391
497
  end