dry-schema 1.13.1 → 1.14.1

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -0
  3. data/README.md +2 -2
  4. data/config/errors.yml +10 -0
  5. data/dry-schema.gemspec +15 -17
  6. data/lib/dry/schema/config.rb +9 -7
  7. data/lib/dry/schema/constants.rb +2 -2
  8. data/lib/dry/schema/dsl.rb +29 -21
  9. data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +1 -1
  10. data/lib/dry/schema/extensions/info/schema_compiler.rb +16 -7
  11. data/lib/dry/schema/extensions/json_schema/schema_compiler.rb +4 -4
  12. data/lib/dry/schema/extensions/monads.rb +1 -1
  13. data/lib/dry/schema/extensions/struct.rb +28 -6
  14. data/lib/dry/schema/key.rb +4 -4
  15. data/lib/dry/schema/key_coercer.rb +3 -3
  16. data/lib/dry/schema/key_map.rb +5 -5
  17. data/lib/dry/schema/key_validator.rb +21 -10
  18. data/lib/dry/schema/macros/core.rb +2 -2
  19. data/lib/dry/schema/macros/dsl.rb +1 -1
  20. data/lib/dry/schema/macros/filled.rb +1 -2
  21. data/lib/dry/schema/macros/hash.rb +1 -1
  22. data/lib/dry/schema/macros/schema.rb +3 -3
  23. data/lib/dry/schema/macros/value.rb +12 -12
  24. data/lib/dry/schema/message.rb +4 -4
  25. data/lib/dry/schema/message_compiler.rb +8 -8
  26. data/lib/dry/schema/message_set.rb +8 -8
  27. data/lib/dry/schema/messages/abstract.rb +12 -12
  28. data/lib/dry/schema/messages/namespaced.rb +1 -1
  29. data/lib/dry/schema/messages/template.rb +2 -2
  30. data/lib/dry/schema/messages/yaml.rb +3 -3
  31. data/lib/dry/schema/path.rb +12 -12
  32. data/lib/dry/schema/predicate.rb +4 -4
  33. data/lib/dry/schema/predicate_registry.rb +2 -2
  34. data/lib/dry/schema/processor.rb +10 -10
  35. data/lib/dry/schema/processor_steps.rb +1 -1
  36. data/lib/dry/schema/result.rb +6 -6
  37. data/lib/dry/schema/rule_applier.rb +1 -1
  38. data/lib/dry/schema/trace.rb +1 -1
  39. data/lib/dry/schema/type_container.rb +2 -2
  40. data/lib/dry/schema/type_registry.rb +1 -1
  41. data/lib/dry/schema/types.rb +1 -1
  42. data/lib/dry/schema/types_merger.rb +18 -62
  43. data/lib/dry/schema/value_coercer.rb +1 -1
  44. data/lib/dry/schema/version.rb +1 -1
  45. data/lib/dry/schema.rb +12 -11
  46. metadata +21 -79
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e1949729f55d95db1df478e3d2bf0077728c2cfaa5a66ac3d6d00bdbaaf55347
4
- data.tar.gz: 574d9ebd59457187c161730f8b2f232d299ff813fb6af10c680b57ce17c5ee46
3
+ metadata.gz: b6967bfe7737c11a9df20fc846698f42a911e454cb8c1621d20bc69d93e53dca
4
+ data.tar.gz: 0a97d750986808ca32535839e779485018d9a7eba1d6a5f09f2128e93f8bbaef
5
5
  SHA512:
6
- metadata.gz: 520e01dc0ab8454bb981fcba032d3a1099fb7fad15b7eaf4008a094fae381178ab389b143a83b2f9f46522722aa14498bcf9a85e9708537d773a3b8f07c81635
7
- data.tar.gz: 6d3c13d9d1c13dd632368fd4a3ff34aaa8fec5573a5001540552eadd3debe413160d54bbc8051822deb029c9bf20ffe301a120dbd97f2a569eac398de767053e
6
+ metadata.gz: b42eb4a52a991fdf5605d8dc69b6b7362578ef140e272a2cff4ef0fc3852e01797714dfbe2507f9aa05ef6885b738413bd8f93536e4aabb8cccbd1c8a23e7cf1
7
+ data.tar.gz: a59cc89f18b981031fc7df87e0c3df9692512617dc0bdc35a417a5dce55196788262969ea742da0957a0616a9a0f648c78b4905abb031977570da0751002572b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
2
 
3
+ ## 1.14.0 2025-01-06
4
+
5
+
6
+ ### Added
7
+
8
+ - Better support for sets in `excluded_from?` and `included_in?` predicates (via #480) (@flash-gordon)
9
+
10
+ ### Fixed
11
+
12
+ - Fix info extension for maybe macro (via #484) (@flash-gordon + @santiagodoldan)
13
+ - Missing message for :uri? predicate (via #477) (@timjnh)
14
+ - JSON schema output for `maybe(:array)` (via #463) (@tomgi)
15
+
16
+ ### Changed
17
+
18
+ - Set minimum Ruby version to 3.1 (@flash-gordon)
19
+ - `KeyValidator` works faster for large schemas (via #461) (@radarek)
20
+
21
+ [Compare v1.13.4...v1.14.0](https://github.com/dry-rb/dry-schema/compare/v1.13.4...v1.14.0)
22
+
23
+ ## 1.13.4 2024-05-22
24
+
25
+
26
+ ### Added
27
+
28
+ - Default error message for `:uri?` (issue #476 via #477) (@timjnh)
29
+
30
+ ### Fixed
31
+
32
+ - Fix json-schema type of objects nested under arrays (issue #400 fixed via #462) (@tomgi)
33
+
34
+ ### Changed
35
+
36
+ - i18n backend is no longer eager-loaded (via 85a9e0b) (@adam12)
37
+
38
+ [Compare v1.13.3...v1.13.4](https://github.com/dry-rb/dry-schema/compare/v1.13.3...v1.13.4)
39
+
40
+ ## 1.13.3 2023-08-26
41
+
42
+
43
+ ### Fixed
44
+
45
+ - Fix struct extension for nested struct definitions (via #466) (@flash-gordon)
46
+
47
+
48
+ [Compare v1.13.2...v1.13.3](https://github.com/dry-rb/dry-schema/compare/v1.13.2...v1.13.3)
49
+
50
+ ## 1.13.2 2023-05-31
51
+
52
+
53
+ ### Fixed
54
+
55
+ - Fix custom predicates setting (via #460) (@solnic)
56
+
57
+
58
+ [Compare v1.13.1...v1.13.2](https://github.com/dry-rb/dry-schema/compare/v1.13.1...v1.13.2)
59
+
3
60
  ## 1.13.1 2023-04-07
4
61
 
5
62
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  [gem]: https://rubygems.org/gems/dry-schema
3
3
  [actions]: https://github.com/dry-rb/dry-schema/actions
4
4
 
5
- # dry-schema [![Gem Version](https://badge.fury.io/rb/dry-schema.svg)][gem] [![CI Status](https://github.com/dry-rb/dry-schema/workflows/ci/badge.svg)][actions]
5
+ # dry-schema [![Gem Version](https://badge.fury.io/rb/dry-schema.svg)][gem] [![CI Status](https://github.com/dry-rb/dry-schema/workflows/CI/badge.svg)][actions]
6
6
 
7
7
  ## Links
8
8
 
@@ -14,7 +14,7 @@
14
14
 
15
15
  This library officially supports the following Ruby versions:
16
16
 
17
- * MRI `>= 3.0.0`
17
+ * MRI `>= 3.1`
18
18
  * jruby `>= 9.4` (not tested on CI)
19
19
 
20
20
  ## License
data/config/errors.yml CHANGED
@@ -105,7 +105,17 @@ en:
105
105
  default: "must be %{size} bytes long"
106
106
  range: "must be within %{size_left} - %{size_right} bytes long"
107
107
 
108
+ uri?: "is not a valid URI"
109
+
110
+ uuid_v1?: "is not a valid UUID"
111
+
112
+ uuid_v2?: "is not a valid UUID"
113
+
114
+ uuid_v3?: "is not a valid UUID"
115
+
108
116
  uuid_v4?: "is not a valid UUID"
109
117
 
118
+ uuid_v5?: "is not a valid UUID"
119
+
110
120
  not:
111
121
  empty?: "cannot be empty"
data/dry-schema.gemspec CHANGED
@@ -21,28 +21,26 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  TEXT
23
23
  spec.homepage = "https://dry-rb.org/gems/dry-schema"
24
- spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-schema.gemspec", "lib/**/*", "config/*.yml"]
24
+ spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-schema.gemspec",
25
+ "lib/**/*", "config/*.yml"]
25
26
  spec.bindir = "bin"
26
27
  spec.executables = []
27
28
  spec.require_paths = ["lib"]
28
29
 
29
- spec.metadata["allowed_push_host"] = "https://rubygems.org"
30
- spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-schema/blob/main/CHANGELOG.md"
31
- spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-schema"
32
- spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-schema/issues"
30
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
31
+ spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-schema/blob/main/CHANGELOG.md"
32
+ spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-schema"
33
+ spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-schema/issues"
34
+ spec.metadata["rubygems_mfa_required"] = "true"
33
35
 
34
- spec.required_ruby_version = ">= 2.7"
36
+ spec.required_ruby_version = ">= 3.1"
35
37
 
36
38
  # to update dependencies edit project.yml
37
- spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
38
- spec.add_runtime_dependency "dry-configurable", "~> 1.0", ">= 1.0.1"
39
- spec.add_runtime_dependency "dry-core", "~> 1.0", "< 2"
40
- spec.add_runtime_dependency "dry-initializer", "~> 3.0"
41
- spec.add_runtime_dependency "dry-logic", ">= 1.4", "< 2"
42
- spec.add_runtime_dependency "dry-types", ">= 1.7", "< 2"
43
- spec.add_runtime_dependency "zeitwerk", "~> 2.6"
44
-
45
- spec.add_development_dependency "bundler"
46
- spec.add_development_dependency "rake"
47
- spec.add_development_dependency "rspec"
39
+ spec.add_dependency "concurrent-ruby", "~> 1.0"
40
+ spec.add_dependency "dry-configurable", "~> 1.0", ">= 1.0.1"
41
+ spec.add_dependency "dry-core", "~> 1.1"
42
+ spec.add_dependency "dry-initializer", "~> 3.2"
43
+ spec.add_dependency "dry-logic", "~> 1.5"
44
+ spec.add_dependency "dry-types", "~> 1.8"
45
+ spec.add_dependency "zeitwerk", "~> 2.6"
48
46
  end
@@ -12,8 +12,8 @@ module Dry
12
12
  #
13
13
  # @api public
14
14
  class Config
15
- include Dry::Configurable
16
- include Dry::Equalizer(:to_h, inspect: false)
15
+ include ::Dry::Configurable
16
+ include ::Dry::Equalizer(:to_h, inspect: false)
17
17
 
18
18
  # @!method predicates
19
19
  #
@@ -22,7 +22,9 @@ module Dry
22
22
  # @return [Schema::PredicateRegistry]
23
23
  #
24
24
  # @api public
25
- setting :predicates, default: Schema::PredicateRegistry.new
25
+ setting :predicates, default: PredicateRegistry.new, constructor: -> predicates {
26
+ predicates.is_a?(PredicateRegistry) ? predicates : PredicateRegistry.new(predicates)
27
+ }
26
28
 
27
29
  # @!method types
28
30
  #
@@ -31,7 +33,7 @@ module Dry
31
33
  # @return [Hash]
32
34
  #
33
35
  # @api public
34
- setting :types, default: Dry::Types
36
+ setting :types, default: ::Dry::Types
35
37
 
36
38
  # @!method messages
37
39
  #
@@ -43,7 +45,7 @@ module Dry
43
45
  setting :messages do
44
46
  setting :backend, default: :yaml
45
47
  setting :namespace
46
- setting :load_paths, default: Set[DEFAULT_MESSAGES_PATH], constructor: :dup.to_proc
48
+ setting :load_paths, default: ::Set[DEFAULT_MESSAGES_PATH], constructor: :dup.to_proc
47
49
  setting :top_namespace, default: DEFAULT_MESSAGES_ROOT
48
50
  setting :default_locale
49
51
  end
@@ -72,9 +74,9 @@ module Dry
72
74
  # Forward to the underlying config object
73
75
  #
74
76
  # @api private
75
- def method_missing(meth, *args, &block)
77
+ def method_missing(meth, ...)
76
78
  super unless config.respond_to?(meth)
77
- config.public_send(meth, *args)
79
+ config.public_send(meth, ...)
78
80
  end
79
81
  end
80
82
  end
@@ -30,10 +30,10 @@ module Dry
30
30
  DEFAULT_MESSAGES_ROOT = "dry_schema"
31
31
 
32
32
  # An error raised when DSL is used in an incorrect way
33
- InvalidSchemaError = Class.new(StandardError)
33
+ InvalidSchemaError = ::Class.new(::StandardError)
34
34
 
35
35
  # An error raised when a localized message cannot be found
36
- MissingMessageError = Class.new(StandardError) do
36
+ MissingMessageError = ::Class.new(::StandardError) do
37
37
  # @api private
38
38
  def initialize(path, paths = [])
39
39
  *rest, rule = path
@@ -37,10 +37,7 @@ module Dry
37
37
  class DSL
38
38
  Types = Schema::Types
39
39
 
40
- extend Dry::Initializer
41
-
42
- # @return [Compiler] The rule compiler object
43
- option :compiler, default: -> { Compiler.new }
40
+ extend ::Dry::Initializer
44
41
 
45
42
  # @return [Compiler] The type of the processor (Params, JSON, or a custom sub-class)
46
43
  option :processor_type, default: -> { Processor }
@@ -81,9 +78,10 @@ module Dry
81
78
  # @return [DSL]
82
79
  #
83
80
  # @api public
84
- def self.new(**options, &block)
81
+ def self.new(**options, &)
85
82
  dsl = super
86
- dsl.instance_eval(&block) if block
83
+ dsl.instance_eval(&) if block_given?
84
+ dsl.instance_variable_set("@compiler", options[:compiler]) if options[:compiler]
87
85
  dsl
88
86
  end
89
87
 
@@ -101,11 +99,21 @@ module Dry
101
99
  # @return [DSL]
102
100
  #
103
101
  # @api public
104
- def configure(&block)
105
- config.configure(&block)
102
+ def configure(&)
103
+ config.configure(&)
106
104
  self
107
105
  end
108
106
 
107
+ # @api private
108
+ def compiler
109
+ @compiler ||= Compiler.new(predicates)
110
+ end
111
+
112
+ # @api private
113
+ def predicates
114
+ @predicates ||= config.predicates
115
+ end
116
+
109
117
  # Return a macro with the provided name
110
118
  #
111
119
  # @param [Symbol] name
@@ -133,8 +141,8 @@ module Dry
133
141
  # @return [Macros::Required]
134
142
  #
135
143
  # @api public
136
- def required(name, &block)
137
- key(name, macro: Macros::Required, &block)
144
+ def required(name, &)
145
+ key(name, macro: Macros::Required, &)
138
146
  end
139
147
 
140
148
  # Define an optional key
@@ -149,8 +157,8 @@ module Dry
149
157
  # @return [Macros::Optional]
150
158
  #
151
159
  # @api public
152
- def optional(name, &block)
153
- key(name, macro: Macros::Optional, &block)
160
+ def optional(name, &)
161
+ key(name, macro: Macros::Optional, &)
154
162
  end
155
163
 
156
164
  # A generic method for defining keys
@@ -162,7 +170,7 @@ module Dry
162
170
  # @return [Macros::Key]
163
171
  #
164
172
  # @api public
165
- def key(name, macro:, &block)
173
+ def key(name, macro:, &)
166
174
  raise ArgumentError, "Key +#{name}+ is not a symbol" unless name.is_a?(::Symbol)
167
175
 
168
176
  set_type(name, Types::Any.meta(default: true))
@@ -174,7 +182,7 @@ module Dry
174
182
  filter_schema_dsl: filter_schema_dsl
175
183
  )
176
184
 
177
- macro.value(&block) if block
185
+ macro.value(&) if block_given?
178
186
  macros << macro
179
187
  macro
180
188
  end
@@ -241,8 +249,8 @@ module Dry
241
249
  # @return [DSL]
242
250
  #
243
251
  # @api public
244
- def before(key, &block)
245
- steps.before(key, &block)
252
+ def before(key, &)
253
+ steps.before(key, &)
246
254
  self
247
255
  end
248
256
 
@@ -256,8 +264,8 @@ module Dry
256
264
  # @return [DSL]
257
265
  #
258
266
  # @api public
259
- def after(key, &block)
260
- steps.after(key, &block)
267
+ def after(key, &)
268
+ steps.after(key, &)
261
269
  self
262
270
  end
263
271
 
@@ -293,8 +301,8 @@ module Dry
293
301
  # @return [Dry::Types::Safe]
294
302
  #
295
303
  # @api private
296
- def new(klass: self.class, **options, &block)
297
- klass.new(**options, processor_type: processor_type, config: config, &block)
304
+ def new(klass: self.class, **options, &)
305
+ klass.new(**options, processor_type: processor_type, config: config, &)
298
306
  end
299
307
 
300
308
  # Set a type for the given key name
@@ -493,7 +501,7 @@ module Dry
493
501
  def default_config
494
502
  parents.each_cons(2) do |left, right|
495
503
  unless left.config == right.config
496
- raise ArgumentError,
504
+ raise ::ArgumentError,
497
505
  "Parent configs differ, left=#{left.inspect}, right=#{right.inspect}"
498
506
  end
499
507
  end
@@ -31,7 +31,7 @@ module Dry
31
31
 
32
32
  # @api private
33
33
  def filter(messages, opts)
34
- Array(messages).flatten.map { |msg| msg unless exclude?(msg, opts) }.compact.uniq
34
+ Array(messages).flatten.reject { |msg| exclude?(msg, opts) }.uniq
35
35
  end
36
36
 
37
37
  # @api private
@@ -70,20 +70,25 @@ module Dry
70
70
 
71
71
  # @api private
72
72
  def visit_implication(node, opts = EMPTY_HASH)
73
- node.each do |el|
74
- visit(el, opts.merge(required: false))
73
+ case node
74
+ in [:not, [:predicate, [:nil?, _]]], el
75
+ visit(el, {**opts, nullable: true})
76
+ else
77
+ node.each do |el|
78
+ visit(el, {**opts, required: false})
79
+ end
75
80
  end
76
81
  end
77
82
 
78
83
  # @api private
79
84
  def visit_each(node, opts = EMPTY_HASH)
80
- visit(node, opts.merge(member: true))
85
+ visit(node, {**opts, member: true})
81
86
  end
82
87
 
83
88
  # @api private
84
89
  def visit_key(node, opts = EMPTY_HASH)
85
90
  name, rest = node
86
- visit(rest, opts.merge(key: name, required: true))
91
+ visit(rest, {**opts, key: name, required: true})
87
92
  end
88
93
 
89
94
  # @api private
@@ -93,19 +98,23 @@ module Dry
93
98
  key = opts[:key]
94
99
 
95
100
  if name.equal?(:key?)
96
- keys[rest[0][1]] = {required: opts.fetch(:required, true)}
101
+ keys[rest[0][1]] = {
102
+ required: opts.fetch(:required, true)
103
+ }
97
104
  else
98
105
  type = PREDICATE_TO_TYPE[name]
99
- assign_type(key, type) if type
106
+ nullable = opts.fetch(:nullable, false)
107
+ assign_type(key, type, nullable) if type
100
108
  end
101
109
  end
102
110
 
103
111
  # @api private
104
- def assign_type(key, type)
112
+ def assign_type(key, type, nullable)
105
113
  if keys[key][:type]
106
114
  keys[key][:member] = type
107
115
  else
108
116
  keys[key][:type] = type
117
+ keys[key][:nullable] = nullable
109
118
  end
110
119
  end
111
120
  end
@@ -9,7 +9,7 @@ module Dry
9
9
  # @api private
10
10
  class SchemaCompiler
11
11
  # An error raised when a predicate cannot be converted
12
- UnknownConversionError = Class.new(StandardError)
12
+ UnknownConversionError = ::Class.new(::StandardError)
13
13
 
14
14
  IDENTITY = ->(v, _) { v }.freeze
15
15
  TO_INTEGER = ->(v, _) { v.to_i }.freeze
@@ -90,14 +90,14 @@ module Dry
90
90
  def visit_set(node, opts = EMPTY_HASH)
91
91
  target = (key = opts[:key]) ? self.class.new(loose: loose?) : self
92
92
 
93
- node.map { |child| target.visit(child, opts) }
93
+ node.map { |child| target.visit(child, opts.except(:member)) }
94
94
 
95
95
  return unless key
96
96
 
97
97
  target_info = opts[:member] ? {items: target.to_h} : target.to_h
98
98
  type = opts[:member] ? "array" : "object"
99
99
 
100
- keys.update(key => {**keys[key], type: type, **target_info})
100
+ merge_opts!(keys[key], {type: type, **target_info})
101
101
  end
102
102
 
103
103
  # @api private
@@ -210,7 +210,7 @@ module Dry
210
210
  orig_type = orig_opts[:type]
211
211
 
212
212
  if orig_type && new_type && orig_type != new_type
213
- new_opts[:type] = [orig_type, new_type]
213
+ new_opts[:type] = [orig_type, new_type].flatten.uniq
214
214
  end
215
215
 
216
216
  orig_opts.merge!(new_opts)
@@ -11,7 +11,7 @@ module Dry
11
11
  #
12
12
  # @api public
13
13
  class Result
14
- include Dry::Monads::Result::Mixin
14
+ include ::Dry::Monads::Result::Mixin
15
15
 
16
16
  # Turn result into a monad
17
17
  #
@@ -5,24 +5,46 @@ require "dry/struct"
5
5
  module Dry
6
6
  module Schema
7
7
  module Macros
8
+ class StructToSchema < ::Dry::Struct::Compiler
9
+ def call(struct)
10
+ visit(struct.to_ast)
11
+ end
12
+
13
+ # strip away structs from AST
14
+ def visit_struct(node)
15
+ _, ast = node
16
+ visit(ast)
17
+ end
18
+ end
19
+
20
+ Hash.option :struct_compiler, default: proc { StructToSchema.new(schema_dsl.config.types) }
21
+
8
22
  Hash.prepend(::Module.new {
9
23
  def call(*args)
10
- if args.size >= 1 && args[0].is_a?(::Class) && args[0] <= ::Dry::Struct
24
+ if args.size >= 1 && struct?(args[0])
11
25
  if block_given?
12
- raise ArgumentError, "blocks are not supported when using "\
26
+ raise ArgumentError, "blocks are not supported when using " \
13
27
  "a struct class (#{name.inspect} => #{args[0]})"
14
28
  end
15
29
 
16
- super(args[0].schema, *args.drop(1))
17
- type(schema_dsl.types[name].constructor(args[0].schema))
30
+ schema = struct_compiler.(args[0])
31
+
32
+ super(schema, *args.drop(1))
33
+ type(schema_dsl.types[name].constructor(schema))
18
34
  else
19
35
  super
20
36
  end
21
37
  end
38
+
39
+ private
40
+
41
+ def struct?(type)
42
+ type.is_a?(::Class) && type <= ::Dry::Struct
43
+ end
22
44
  })
23
45
  end
24
46
 
25
- PredicateInferrer::Compiler.send(:alias_method, :visit_struct, :visit_hash)
26
- PrimitiveInferrer::Compiler.send(:alias_method, :visit_struct, :visit_hash)
47
+ PredicateInferrer::Compiler.alias_method(:visit_struct, :visit_hash)
48
+ PrimitiveInferrer::Compiler.alias_method(:visit_struct, :visit_hash)
27
49
  end
28
50
  end
@@ -6,11 +6,11 @@ module Dry
6
6
  #
7
7
  # @api public
8
8
  class Key
9
- extend Dry::Core::Cache
9
+ extend ::Dry::Core::Cache
10
10
 
11
11
  DEFAULT_COERCER = :itself.to_proc.freeze
12
12
 
13
- include Dry.Equalizer(:name, :coercer)
13
+ include ::Dry.Equalizer(:name, :coercer)
14
14
 
15
15
  # @return [Symbol] The key identifier
16
16
  attr_reader :id
@@ -90,7 +90,7 @@ module Dry
90
90
  #
91
91
  # @api private
92
92
  class Hash < self
93
- include Dry.Equalizer(:name, :members, :coercer)
93
+ include ::Dry.Equalizer(:name, :members, :coercer)
94
94
 
95
95
  # @api private
96
96
  attr_reader :members
@@ -137,7 +137,7 @@ module Dry
137
137
  #
138
138
  # @api private
139
139
  class Array < self
140
- include Dry.Equalizer(:name, :member, :coercer)
140
+ include ::Dry.Equalizer(:name, :member, :coercer)
141
141
 
142
142
  attr_reader :member
143
143
 
@@ -9,7 +9,7 @@ module Dry
9
9
  #
10
10
  # @api private
11
11
  class KeyCoercer
12
- extend Dry::Core::Cache
12
+ extend ::Dry::Core::Cache
13
13
  include ::Dry::Equalizer(:key_map, :coercer)
14
14
 
15
15
  TO_SYM = :to_sym.to_proc.freeze
@@ -27,8 +27,8 @@ module Dry
27
27
  end
28
28
 
29
29
  # @api private
30
- def initialize(key_map, &coercer)
31
- @key_map = key_map.coercible(&coercer)
30
+ def initialize(key_map, &)
31
+ @key_map = key_map.coercible(&)
32
32
  end
33
33
 
34
34
  # @api private
@@ -13,10 +13,10 @@ module Dry
13
13
  #
14
14
  # @api public
15
15
  class KeyMap
16
- extend Dry::Core::Cache
16
+ extend ::Dry::Core::Cache
17
17
 
18
- include Dry.Equalizer(:keys)
19
- include Enumerable
18
+ include ::Dry.Equalizer(:keys)
19
+ include ::Enumerable
20
20
 
21
21
  # @return [Array<Key>] A list of defined key objects
22
22
  attr_reader :keys
@@ -105,8 +105,8 @@ module Dry
105
105
  # Iterate over keys
106
106
  #
107
107
  # @api public
108
- def each(&block)
109
- keys.each(&block)
108
+ def each(&)
109
+ keys.each(&)
110
110
  end
111
111
 
112
112
  # Return a new key map merged with the provided one
@@ -7,10 +7,10 @@ module Dry
7
7
  module Schema
8
8
  # @api private
9
9
  class KeyValidator
10
- extend Dry::Initializer
10
+ extend ::Dry::Initializer
11
11
 
12
- INDEX_REGEX = /\[\d+\]/.freeze
13
- DIGIT_REGEX = /\A\d+\z/.freeze
12
+ INDEX_REGEX = /\[\d+\]/
13
+ DIGIT_REGEX = /\A\d+\z/
14
14
  BRACKETS = "[]"
15
15
 
16
16
  # @api private
@@ -21,7 +21,7 @@ module Dry
21
21
  input = result.to_h
22
22
 
23
23
  input_paths = key_paths(input)
24
- key_paths = key_map.to_dot_notation
24
+ key_paths = key_map.to_dot_notation.sort
25
25
 
26
26
  input_paths.each do |path|
27
27
  error_path = validate_path(key_paths, path)
@@ -40,20 +40,31 @@ module Dry
40
40
  def validate_path(key_paths, path)
41
41
  if path[INDEX_REGEX]
42
42
  key = path.gsub(INDEX_REGEX, BRACKETS)
43
-
44
- if key_paths.none? { paths_match?(key, _1) }
43
+ if none_key_paths_match?(key_paths, key)
45
44
  arr = path.gsub(INDEX_REGEX) { ".#{_1[1]}" }
46
45
  arr.split(DOT).map { DIGIT_REGEX.match?(_1) ? Integer(_1, 10) : _1.to_sym }
47
46
  end
48
- elsif key_paths.none? { paths_match?(path, _1) }
47
+ elsif none_key_paths_match?(key_paths, path)
49
48
  path
50
49
  end
51
50
  end
52
51
 
53
52
  # @api private
54
- def paths_match?(input_path, key_path)
55
- residue = key_path.sub(input_path, "")
56
- residue.empty? || residue.start_with?(DOT, BRACKETS)
53
+ def none_key_paths_match?(key_paths, path)
54
+ !any_key_paths_match?(key_paths, path)
55
+ end
56
+
57
+ # @api private
58
+ def any_key_paths_match?(key_paths, path)
59
+ find_path(key_paths, path, false) ||
60
+ find_path(key_paths, path + DOT, true) ||
61
+ find_path(key_paths, path + BRACKETS, true)
62
+ end
63
+
64
+ # @api private
65
+ def find_path(key_paths, path, prefix_match)
66
+ key = key_paths.bsearch { |key_path| key_path >= path }
67
+ prefix_match ? key&.start_with?(path) : key == path
57
68
  end
58
69
 
59
70
  # @api private
@@ -19,10 +19,10 @@ module Dry
19
19
  option :compiler, default: proc { Compiler.new }
20
20
 
21
21
  # @api private
22
- option :trace, default: proc { Trace.new }
22
+ option :schema_dsl, optional: true
23
23
 
24
24
  # @api private
25
- option :schema_dsl, optional: true
25
+ option :trace, default: proc { Trace.new(schema_dsl&.compiler || Compiler.new) }
26
26
 
27
27
  # @api private
28
28
  def new(**options)