dry-schema 1.8.0 → 1.9.2

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -1
  3. data/README.md +4 -4
  4. data/dry-schema.gemspec +2 -2
  5. data/lib/dry/schema/compiler.rb +1 -1
  6. data/lib/dry/schema/dsl.rb +7 -4
  7. data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +9 -4
  8. data/lib/dry/schema/extensions/hints.rb +11 -9
  9. data/lib/dry/schema/extensions/info/schema_compiler.rb +10 -1
  10. data/lib/dry/schema/extensions/json_schema/schema_compiler.rb +232 -0
  11. data/lib/dry/schema/extensions/json_schema.rb +29 -0
  12. data/lib/dry/schema/extensions/struct.rb +1 -1
  13. data/lib/dry/schema/extensions.rb +4 -0
  14. data/lib/dry/schema/key.rb +75 -74
  15. data/lib/dry/schema/key_coercer.rb +2 -2
  16. data/lib/dry/schema/key_validator.rb +44 -23
  17. data/lib/dry/schema/macros/array.rb +4 -0
  18. data/lib/dry/schema/macros/core.rb +1 -1
  19. data/lib/dry/schema/macros/dsl.rb +17 -15
  20. data/lib/dry/schema/macros/hash.rb +1 -1
  21. data/lib/dry/schema/macros/key.rb +2 -2
  22. data/lib/dry/schema/macros/schema.rb +2 -0
  23. data/lib/dry/schema/macros/value.rb +7 -0
  24. data/lib/dry/schema/message/or/multi_path.rb +7 -5
  25. data/lib/dry/schema/message_compiler.rb +13 -10
  26. data/lib/dry/schema/messages/i18n.rb +98 -96
  27. data/lib/dry/schema/messages/namespaced.rb +6 -0
  28. data/lib/dry/schema/messages/yaml.rb +165 -158
  29. data/lib/dry/schema/predicate.rb +2 -2
  30. data/lib/dry/schema/predicate_inferrer.rb +2 -0
  31. data/lib/dry/schema/primitive_inferrer.rb +2 -0
  32. data/lib/dry/schema/processor.rb +2 -2
  33. data/lib/dry/schema/result.rb +5 -7
  34. data/lib/dry/schema/trace.rb +5 -1
  35. data/lib/dry/schema/type_registry.rb +1 -2
  36. data/lib/dry/schema/version.rb +1 -1
  37. metadata +11 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc91fbbcd77a2535c39ac072f7725e2cc89cf40fb1710c1f7f2f32867d37351e
4
- data.tar.gz: a725bbc8ae7735fb576b827c5ee4dc00235e51287e7fb20882d8235fc61d72f5
3
+ metadata.gz: c27dabeb33b1644739f90d7bbac22dfe85ae79d742da8376cad64bdfff46c514
4
+ data.tar.gz: 5b4df5b8215933c70fd3c04fd3a8cd74c0ff7bdfe4919110f95753348ce28726
5
5
  SHA512:
6
- metadata.gz: 1dcb14a96c4d9e1c38ca7bafaf04a621ee71cffc75bb4f3328d46752ec291f4ee580fea7f21bc4b430277af601ca31e29c5358d3c72d38ec53e128b411921245
7
- data.tar.gz: 27d66147c3be3d089d92b56ddec1b00a230d83e04606a52c14c10cb0f40648b8beb893f874e00916c5450769b1bdaf72c0df54e3e89464f96bc96986904fe465
6
+ metadata.gz: 9111228ae652344f54044d93fd7516d67cdefa0789587b786d58291d40f9c61c009207c5b3ef68d7302e8e889a125e51fbd4bfa3a5ee6b8b2629a2e06cf7a538
7
+ data.tar.gz: bc1701018a9c35fb618c92a50fb4d68f24957f3d22431e343457dba65645d6476a5c7378620fe934b8520cfd20d62a2d2de1ac5c04af1f0130dfba5de86cb2f9
data/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
2
 
3
+ ## 1.9.2 2022-05-28
4
+
5
+
6
+ ### Fixed
7
+
8
+ - Fix loose JSON schemas for nested hashes (via #401) (@tomdalling)
9
+ - Correct spelling error 'mininum' to 'minimum' in json-schema extension (via #404) (@svenanderzen)
10
+
11
+ ### Changed
12
+
13
+ - [performance] YAML message backend allocates less strings (via #399) (@casperisfine)
14
+
15
+ [Compare v1.9.1...v1.9.2](https://github.com/dry-rb/dry-schema/compare/v1.9.1...v1.9.2)
16
+
17
+ ## 1.9.1 2022-02-17
18
+
19
+
20
+ ### Fixed
21
+
22
+ - Namespaced messages no longer crashes in certain scenarios (see dry-rb/dry-validation#692 fixed via #398) (@krekoten)
23
+
24
+
25
+ [Compare v1.9.0...v1.9.1](https://github.com/dry-rb/dry-schema/compare/v1.9.0...v1.9.1)
26
+
27
+ ## 1.9.0 2022-02-15
28
+
29
+
30
+ ### Added
31
+
32
+ - [EXPERIMENTAL] `json_schema` extension which allows you to convert a schema into a JSON schema (via #369) (@ianks)
33
+
34
+ ### Fixed
35
+
36
+ - Composing schemas no longer crashes in certain scenarios (issue #342 fixed via #366) (@vsuhachev)
37
+ - Fix info extension for typed arrays (issue #394 fixed via #397) (@CandyFet)
38
+
39
+
40
+ [Compare v1.8.0...v1.9.0](https://github.com/dry-rb/dry-schema/compare/v1.8.0...v1.9.0)
41
+
3
42
  ## 1.8.0 2021-09-12
4
43
 
5
44
 
@@ -121,7 +160,7 @@ This release ships with a bunch of internal refactorings that should improve per
121
160
  - Key validation works correctly with a non-nested maybe hashes (issue #311 fixed via #312) (@svobom57)
122
161
 
123
162
 
124
- [Compare v1.5.3...master](https://github.com/dry-rb/dry-schema/compare/v1.5.3...master)
163
+ [Compare v1.5.3...main](https://github.com/dry-rb/dry-schema/compare/v1.5.3...main)
125
164
 
126
165
  ## 1.5.3 2020-08-21
127
166
 
data/README.md CHANGED
@@ -8,10 +8,10 @@
8
8
  # dry-schema [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
9
9
 
10
10
  [![Gem Version](https://badge.fury.io/rb/dry-schema.svg)][gem]
11
- [![CI Status](https://github.com/dry-rb/dry-schema/workflows/CI/badge.svg)][actions]
11
+ [![CI Status](https://github.com/dry-rb/dry-schema/workflows/ci/badge.svg)][actions]
12
12
  [![Codacy Badge](https://api.codacy.com/project/badge/Grade/961f5c776f1d49218b2cede3745e059c)][codacy]
13
13
  [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/961f5c776f1d49218b2cede3745e059c)][codacy]
14
- [![Inline docs](http://inch-ci.org/github/dry-rb/dry-schema.svg?branch=master)][inchpages]
14
+ [![Inline docs](http://inch-ci.org/github/dry-rb/dry-schema.svg?branch=main)][inchpages]
15
15
 
16
16
  ## Links
17
17
 
@@ -22,8 +22,8 @@
22
22
 
23
23
  This library officially supports the following Ruby versions:
24
24
 
25
- * MRI `>= 2.6.0`
26
- * ~~jruby~~ `>= 9.3` (we are waiting for [2.6 support](https://github.com/jruby/jruby/issues/6161))
25
+ * MRI `>= 2.7.0`
26
+ * jruby `>= 9.3` (postponed until 2.7 is supported)
27
27
 
28
28
  ## License
29
29
 
data/dry-schema.gemspec CHANGED
@@ -27,11 +27,11 @@ Gem::Specification.new do |spec|
27
27
  spec.require_paths = ["lib"]
28
28
 
29
29
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
30
- spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-schema/blob/master/CHANGELOG.md"
30
+ spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-schema/blob/main/CHANGELOG.md"
31
31
  spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-schema"
32
32
  spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-schema/issues"
33
33
 
34
- spec.required_ruby_version = ">= 2.6.0"
34
+ spec.required_ruby_version = ">= 2.7.0"
35
35
 
36
36
  # to update dependencies edit project.yml
37
37
  spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
@@ -47,7 +47,7 @@ module Dry
47
47
  # @return [Boolean]
48
48
  #
49
49
  # @api private
50
- def supports?(predicate)
50
+ def support?(predicate)
51
51
  predicates.key?(predicate)
52
52
  end
53
53
  end
@@ -80,8 +80,10 @@ module Dry
80
80
  # Build a new DSL object and evaluate provided block
81
81
  #
82
82
  # @param [Hash] options
83
- # @option options [Class] :processor The processor type (`Params`, `JSON` or a custom sub-class)
84
- # @option options [Compiler] :compiler An instance of a rule compiler (must be compatible with `Schema::Compiler`) (optional)
83
+ # @option options [Class] :processor The processor type
84
+ # (`Params`, `JSON` or a custom sub-class)
85
+ # @option options [Compiler] :compiler An instance of a rule compiler
86
+ # (must be compatible with `Schema::Compiler`) (optional)
85
87
  # @option options [Array[DSL]] :parent One or more instances of the parent DSL (optional)
86
88
  # @option options [Config] :config A configuration object (optional)
87
89
  #
@@ -168,7 +170,8 @@ module Dry
168
170
  # A generic method for defining keys
169
171
  #
170
172
  # @param [Symbol] name The key name
171
- # @param [Class] macro The macro sub-class (ie `Macros::Required` or any other `Macros::Key` subclass)
173
+ # @param [Class] macro The macro sub-class (ie `Macros::Required` or
174
+ # any other `Macros::Key` subclass)
172
175
  #
173
176
  # @return [Macros::Key]
174
177
  #
@@ -383,7 +386,7 @@ module Dry
383
386
  #
384
387
  # @api protected
385
388
  def rules
386
- parent_rules.merge(macros.map { |m| [m.name, m.to_rule] }.to_h.compact)
389
+ parent_rules.merge(macros.to_h { [_1.name, _1.to_rule] }.compact)
387
390
  end
388
391
 
389
392
  # Build a key map from defined types
@@ -35,12 +35,14 @@ module Dry
35
35
  end
36
36
 
37
37
  # @api private
38
+ # rubocop: disable Metrics/AbcSize
39
+ # rubocop: disable Metrics/PerceivedComplexity
40
+ # rubocop: disable Metrics/CyclomaticComplexity
38
41
  def exclude?(messages, opts)
39
42
  Array(messages).all? do |msg|
40
- hints = opts
41
- .hints
42
- .reject { |hint| msg == hint }
43
- .reject { |hint| hint.predicate == :filled? }
43
+ hints = opts.hints.reject { |h|
44
+ msg.eql?(h) || h.predicate.eql?(:filled?)
45
+ }
44
46
 
45
47
  key_failure = opts.key_failure?(msg.path)
46
48
  predicate = msg.predicate
@@ -52,6 +54,9 @@ module Dry
52
54
  HINT_OTHER_EXCLUSION.include?(predicate)
53
55
  end
54
56
  end
57
+ # rubocop: enable Metrics/CyclomaticComplexity
58
+ # rubocop: enable Metrics/PerceivedComplexity
59
+ # rubocop: enable Metrics/AbcSize
55
60
 
56
61
  # @api private
57
62
  def message_type(options)
@@ -22,17 +22,19 @@ module Dry
22
22
  # @see Message::Or
23
23
  #
24
24
  # @api public
25
- class Or::SinglePath
26
- # @api private
27
- def hint?
28
- false
25
+ module Or
26
+ class SinglePath
27
+ # @api private
28
+ def hint?
29
+ false
30
+ end
29
31
  end
30
- end
31
32
 
32
- class Or::MultiPath
33
- # @api private
34
- def hint?
35
- false
33
+ class MultiPath
34
+ # @api private
35
+ def hint?
36
+ false
37
+ end
36
38
  end
37
39
  end
38
40
 
@@ -96,7 +96,16 @@ module Dry
96
96
  keys[rest[0][1]] = {required: opts.fetch(:required, true)}
97
97
  else
98
98
  type = PREDICATE_TO_TYPE[name]
99
- keys[key][:type] = type if type
99
+ assign_type(key, type) if type
100
+ end
101
+ end
102
+
103
+ # @api private
104
+ def assign_type(key, type)
105
+ if keys[key][:type]
106
+ keys[key][:member] = type
107
+ else
108
+ keys[key][:type] = type
100
109
  end
101
110
  end
102
111
  end
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema/constants"
4
+
5
+ module Dry
6
+ module Schema
7
+ # @api private
8
+ module JSONSchema
9
+ # @api private
10
+ class SchemaCompiler
11
+ # An error raised when a predicate cannot be converted
12
+ UnknownConversionError = Class.new(StandardError)
13
+
14
+ IDENTITY = ->(v, _) { v }.freeze
15
+ TO_INTEGER = ->(v, _) { v.to_i }.freeze
16
+
17
+ PREDICATE_TO_TYPE = {
18
+ array?: {type: "array"},
19
+ bool?: {type: "boolean"},
20
+ date?: {type: "string", format: "date"},
21
+ date_time?: {type: "string", format: "date-time"},
22
+ decimal?: {type: "number"},
23
+ float?: {type: "number"},
24
+ hash?: {type: "object"},
25
+ int?: {type: "integer"},
26
+ nil?: {type: "null"},
27
+ str?: {type: "string"},
28
+ time?: {type: "string", format: "time"},
29
+ min_size?: {minLength: TO_INTEGER},
30
+ max_size?: {maxLength: TO_INTEGER},
31
+ included_in?: {enum: ->(v, _) { v.to_a }},
32
+ filled?: EMPTY_HASH,
33
+ uri?: {format: "uri"},
34
+ uuid_v1?: {
35
+ pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-1[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
36
+ },
37
+ uuid_v2?: {
38
+ pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-2[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
39
+ },
40
+ uuid_v3?: {
41
+ pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
42
+ },
43
+ uuid_v4?: {
44
+ pattern: "^[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}$"
45
+ },
46
+ uuid_v5?: {
47
+ pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
48
+ },
49
+ gt?: {exclusiveMinimum: IDENTITY},
50
+ gteq?: {minimum: IDENTITY},
51
+ lt?: {exclusiveMaximum: IDENTITY},
52
+ lteq?: {maximum: IDENTITY},
53
+ odd?: {type: "integer", not: {multipleOf: 2}},
54
+ even?: {type: "integer", multipleOf: 2}
55
+ }.freeze
56
+
57
+ # @api private
58
+ attr_reader :keys, :required
59
+
60
+ # @api private
61
+ def initialize(root: false, loose: false)
62
+ @keys = EMPTY_HASH.dup
63
+ @required = Set.new
64
+ @root = root
65
+ @loose = loose
66
+ end
67
+
68
+ # @api private
69
+ def to_hash
70
+ result = {}
71
+ result[:$schema] = "http://json-schema.org/draft-06/schema#" if root?
72
+ result.merge!(type: "object", properties: keys, required: required.to_a)
73
+ result
74
+ end
75
+
76
+ alias_method :to_h, :to_hash
77
+
78
+ # @api private
79
+ def call(ast)
80
+ visit(ast)
81
+ end
82
+
83
+ # @api private
84
+ def visit(node, opts = EMPTY_HASH)
85
+ meth, rest = node
86
+ public_send(:"visit_#{meth}", rest, opts)
87
+ end
88
+
89
+ # @api private
90
+ def visit_set(node, opts = EMPTY_HASH)
91
+ target = (key = opts[:key]) ? self.class.new(loose: loose?) : self
92
+
93
+ node.map { |child| target.visit(child, opts) }
94
+
95
+ return unless key
96
+
97
+ target_info = opts[:member] ? {items: target.to_h} : target.to_h
98
+ type = opts[:member] ? "array" : "object"
99
+
100
+ keys.update(key => {**keys[key], type: type, **target_info})
101
+ end
102
+
103
+ # @api private
104
+ def visit_and(node, opts = EMPTY_HASH)
105
+ left, right = node
106
+
107
+ # We need to know the type first to apply filled macro
108
+ if left[1][0] == :filled?
109
+ visit(right, opts)
110
+ visit(left, opts)
111
+ else
112
+ visit(left, opts)
113
+ visit(right, opts)
114
+ end
115
+ end
116
+
117
+ # @api private
118
+ def visit_implication(node, opts = EMPTY_HASH)
119
+ node.each do |el|
120
+ visit(el, **opts, required: false)
121
+ end
122
+ end
123
+
124
+ # @api private
125
+ def visit_each(node, opts = EMPTY_HASH)
126
+ visit(node, opts.merge(member: true))
127
+ end
128
+
129
+ # @api private
130
+ def visit_key(node, opts = EMPTY_HASH)
131
+ name, rest = node
132
+
133
+ if opts.fetch(:required, :true)
134
+ required << name.to_s
135
+ else
136
+ opts.delete(:required)
137
+ end
138
+
139
+ visit(rest, opts.merge(key: name))
140
+ end
141
+
142
+ # @api private
143
+ def visit_not(node, opts = EMPTY_HASH)
144
+ _name, rest = node
145
+
146
+ visit_predicate(rest, opts)
147
+ end
148
+
149
+ # @api private
150
+ def visit_predicate(node, opts = EMPTY_HASH)
151
+ name, rest = node
152
+
153
+ if name.equal?(:key?)
154
+ prop_name = rest[0][1]
155
+ keys[prop_name] = {}
156
+ else
157
+ target = keys[opts[:key]]
158
+ type_opts = fetch_type_opts_for_predicate(name, rest, target)
159
+
160
+ if target[:type]&.include?("array")
161
+ target[:items] ||= {}
162
+ merge_opts!(target[:items], type_opts)
163
+ else
164
+ merge_opts!(target, type_opts)
165
+ end
166
+ end
167
+ end
168
+
169
+ # @api private
170
+ def fetch_type_opts_for_predicate(name, rest, target)
171
+ type_opts = PREDICATE_TO_TYPE.fetch(name) do
172
+ raise_unknown_conversion_error!(:predicate, name) unless loose?
173
+
174
+ EMPTY_HASH
175
+ end.dup
176
+ type_opts.transform_values! { |v| v.respond_to?(:call) ? v.call(rest[0][1], target) : v }
177
+ type_opts.merge!(fetch_filled_options(target[:type], target)) if name == :filled?
178
+ type_opts
179
+ end
180
+
181
+ # @api private
182
+ def fetch_filled_options(type, _target)
183
+ case type
184
+ when "string"
185
+ {minLength: 1}
186
+ when "array"
187
+ raise_unknown_conversion_error!(:type, :array) unless loose?
188
+
189
+ {not: {type: "null"}}
190
+ else
191
+ {not: {type: "null"}}
192
+ end
193
+ end
194
+
195
+ # @api private
196
+ def merge_opts!(orig_opts, new_opts)
197
+ new_type = new_opts[:type]
198
+ orig_type = orig_opts[:type]
199
+
200
+ if orig_type && new_type && orig_type != new_type
201
+ new_opts[:type] = [orig_type, new_type]
202
+ end
203
+
204
+ orig_opts.merge!(new_opts)
205
+ end
206
+
207
+ # @api private
208
+ def root?
209
+ @root
210
+ end
211
+
212
+ # @api private
213
+ def loose?
214
+ @loose
215
+ end
216
+
217
+ def raise_unknown_conversion_error!(type, name)
218
+ message = <<~MSG
219
+ Could not find an equivalent conversion for #{type} #{name.inspect}.
220
+
221
+ This means that your generated JSON schema may be missing this validation.
222
+
223
+ You can ignore this by generating the schema in "loose" mode, i.e.:
224
+ my_schema.json_schema(loose: true)
225
+ MSG
226
+
227
+ raise UnknownConversionError, message.chomp
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema/extensions/json_schema/schema_compiler"
4
+
5
+ module Dry
6
+ module Schema
7
+ # JSONSchema extension
8
+ #
9
+ # @api public
10
+ module JSONSchema
11
+ module SchemaMethods
12
+ # Convert the schema into a JSON schema hash
13
+ #
14
+ # @param [Symbol] loose Compile the schema in "loose" mode
15
+ #
16
+ # @return [Hash<Symbol=>Hash>]
17
+ #
18
+ # @api public
19
+ def json_schema(loose: false)
20
+ compiler = SchemaCompiler.new(root: true, loose: loose)
21
+ compiler.call(to_ast)
22
+ compiler.to_hash
23
+ end
24
+ end
25
+ end
26
+
27
+ Processor.include(JSONSchema::SchemaMethods)
28
+ end
29
+ end
@@ -18,7 +18,7 @@ module Dry
18
18
  end
19
19
 
20
20
  super(args[0].schema, *args.drop(1))
21
- type(schema_dsl.types[name].constructor(args[0]))
21
+ type(schema_dsl.types[name].constructor(args[0].schema))
22
22
  else
23
23
  super
24
24
  end
@@ -15,3 +15,7 @@ end
15
15
  Dry::Schema.register_extension(:info) do
16
16
  require "dry/schema/extensions/info"
17
17
  end
18
+
19
+ Dry::Schema.register_extension(:json_schema) do
20
+ require "dry/schema/extensions/json_schema"
21
+ end
@@ -85,98 +85,99 @@ module Dry
85
85
  def coerced_name
86
86
  @__coerced_name__ ||= coercer[name]
87
87
  end
88
- end
89
-
90
- # A specialized key type which handles nested hashes
91
- #
92
- # @api private
93
- class Key::Hash < Key
94
- include Dry.Equalizer(:name, :members, :coercer)
95
88
 
89
+ # A specialized key type which handles nested hashes
90
+ #
96
91
  # @api private
97
- attr_reader :members
92
+ class Hash < self
93
+ include Dry.Equalizer(:name, :members, :coercer)
98
94
 
99
- # @api private
100
- def initialize(id, members:, **opts)
101
- super(id, **opts)
102
- @members = members
103
- end
95
+ # @api private
96
+ attr_reader :members
104
97
 
105
- # @api private
106
- def read(source)
107
- super if source.is_a?(::Hash)
108
- end
98
+ # @api private
99
+ def initialize(id, members:, **opts)
100
+ super(id, **opts)
101
+ @members = members
102
+ end
109
103
 
110
- def write(source, target)
111
- read(source) { |value|
112
- target[coerced_name] = value.is_a?(::Hash) ? members.write(value) : value
113
- }
114
- end
104
+ # @api private
105
+ def read(source)
106
+ super if source.is_a?(::Hash)
107
+ end
115
108
 
116
- # @api private
117
- def coercible(&coercer)
118
- new(coercer: coercer, members: members.coercible(&coercer))
119
- end
109
+ def write(source, target)
110
+ read(source) { |value|
111
+ target[coerced_name] = value.is_a?(::Hash) ? members.write(value) : value
112
+ }
113
+ end
120
114
 
121
- # @api private
122
- def stringified
123
- new(name: name.to_s, members: members.stringified)
124
- end
115
+ # @api private
116
+ def coercible(&coercer)
117
+ new(coercer: coercer, members: members.coercible(&coercer))
118
+ end
125
119
 
126
- # @api private
127
- def to_dot_notation
128
- [name].product(members.flat_map(&:to_dot_notation)).map { |e| e.join(DOT) }
129
- end
120
+ # @api private
121
+ def stringified
122
+ new(name: name.to_s, members: members.stringified)
123
+ end
130
124
 
131
- # @api private
132
- def dump
133
- {name => members.map(&:dump)}
125
+ # @api private
126
+ def to_dot_notation
127
+ [name].product(members.flat_map(&:to_dot_notation)).map { |e| e.join(DOT) }
128
+ end
129
+
130
+ # @api private
131
+ def dump
132
+ {name => members.map(&:dump)}
133
+ end
134
134
  end
135
- end
136
135
 
137
- # A specialized key type which handles nested arrays
138
- #
139
- # @api private
140
- class Key::Array < Key
141
- include Dry.Equalizer(:name, :member, :coercer)
136
+ # A specialized key type which handles nested arrays
137
+ #
138
+ # @api private
139
+ class Array < self
140
+ include Dry.Equalizer(:name, :member, :coercer)
142
141
 
143
- attr_reader :member
142
+ attr_reader :member
144
143
 
145
- # @api private
146
- def initialize(id, member:, **opts)
147
- super(id, **opts)
148
- @member = member
149
- end
144
+ # @api private
145
+ def initialize(id, member:, **opts)
146
+ super(id, **opts)
147
+ @member = member
148
+ end
150
149
 
151
- # @api private
152
- def write(source, target)
153
- read(source) { |value|
154
- target[coerced_name] = if value.is_a?(::Array)
155
- value.map { |el| el.is_a?(::Hash) ? member.write(el) : el }
156
- else
157
- value
158
- end
159
- }
160
- end
150
+ # @api private
151
+ def write(source, target)
152
+ read(source) { |value|
153
+ target[coerced_name] =
154
+ if value.is_a?(::Array)
155
+ value.map { |el| el.is_a?(::Hash) ? member.write(el) : el }
156
+ else
157
+ value
158
+ end
159
+ }
160
+ end
161
161
 
162
- # @api private
163
- def coercible(&coercer)
164
- new(coercer: coercer, member: member.coercible(&coercer))
165
- end
162
+ # @api private
163
+ def coercible(&coercer)
164
+ new(coercer: coercer, member: member.coercible(&coercer))
165
+ end
166
166
 
167
- # @api private
168
- def stringified
169
- new(name: name.to_s, member: member.stringified)
170
- end
167
+ # @api private
168
+ def stringified
169
+ new(name: name.to_s, member: member.stringified)
170
+ end
171
171
 
172
- # @api private
173
- def to_dot_notation
174
- [:"#{name}[]"].product(member.to_dot_notation).map { |el| el.join(DOT) }
175
- end
172
+ # @api private
173
+ def to_dot_notation
174
+ [:"#{name}[]"].product(member.to_dot_notation).map { |el| el.join(DOT) }
175
+ end
176
176
 
177
- # @api private
178
- def dump
179
- [name, member.dump]
177
+ # @api private
178
+ def dump
179
+ [name, member.dump]
180
+ end
180
181
  end
181
182
  end
182
183
  end