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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -1
- data/README.md +4 -4
- data/dry-schema.gemspec +2 -2
- data/lib/dry/schema/compiler.rb +1 -1
- data/lib/dry/schema/dsl.rb +7 -4
- data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +9 -4
- data/lib/dry/schema/extensions/hints.rb +11 -9
- data/lib/dry/schema/extensions/info/schema_compiler.rb +10 -1
- data/lib/dry/schema/extensions/json_schema/schema_compiler.rb +232 -0
- data/lib/dry/schema/extensions/json_schema.rb +29 -0
- data/lib/dry/schema/extensions/struct.rb +1 -1
- data/lib/dry/schema/extensions.rb +4 -0
- data/lib/dry/schema/key.rb +75 -74
- data/lib/dry/schema/key_coercer.rb +2 -2
- data/lib/dry/schema/key_validator.rb +44 -23
- data/lib/dry/schema/macros/array.rb +4 -0
- data/lib/dry/schema/macros/core.rb +1 -1
- data/lib/dry/schema/macros/dsl.rb +17 -15
- data/lib/dry/schema/macros/hash.rb +1 -1
- data/lib/dry/schema/macros/key.rb +2 -2
- data/lib/dry/schema/macros/schema.rb +2 -0
- data/lib/dry/schema/macros/value.rb +7 -0
- data/lib/dry/schema/message/or/multi_path.rb +7 -5
- data/lib/dry/schema/message_compiler.rb +13 -10
- data/lib/dry/schema/messages/i18n.rb +98 -96
- data/lib/dry/schema/messages/namespaced.rb +6 -0
- data/lib/dry/schema/messages/yaml.rb +165 -158
- data/lib/dry/schema/predicate.rb +2 -2
- data/lib/dry/schema/predicate_inferrer.rb +2 -0
- data/lib/dry/schema/primitive_inferrer.rb +2 -0
- data/lib/dry/schema/processor.rb +2 -2
- data/lib/dry/schema/result.rb +5 -7
- data/lib/dry/schema/trace.rb +5 -1
- data/lib/dry/schema/type_registry.rb +1 -2
- data/lib/dry/schema/version.rb +1 -1
- metadata +11 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c27dabeb33b1644739f90d7bbac22dfe85ae79d742da8376cad64bdfff46c514
|
4
|
+
data.tar.gz: 5b4df5b8215933c70fd3c04fd3a8cd74c0ff7bdfe4919110f95753348ce28726
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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...
|
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 [][chat]
|
9
9
|
|
10
10
|
[][gem]
|
11
|
-
[][actions]
|
12
12
|
[][codacy]
|
13
13
|
[][codacy]
|
14
|
-
[][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.
|
26
|
-
*
|
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/
|
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.
|
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"
|
data/lib/dry/schema/compiler.rb
CHANGED
data/lib/dry/schema/dsl.rb
CHANGED
@@ -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
|
84
|
-
#
|
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
|
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.
|
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
|
-
.
|
42
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
module Or
|
26
|
+
class SinglePath
|
27
|
+
# @api private
|
28
|
+
def hint?
|
29
|
+
false
|
30
|
+
end
|
29
31
|
end
|
30
|
-
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
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
|
data/lib/dry/schema/key.rb
CHANGED
@@ -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
|
-
|
92
|
+
class Hash < self
|
93
|
+
include Dry.Equalizer(:name, :members, :coercer)
|
98
94
|
|
99
|
-
|
100
|
-
|
101
|
-
super(id, **opts)
|
102
|
-
@members = members
|
103
|
-
end
|
95
|
+
# @api private
|
96
|
+
attr_reader :members
|
104
97
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
98
|
+
# @api private
|
99
|
+
def initialize(id, members:, **opts)
|
100
|
+
super(id, **opts)
|
101
|
+
@members = members
|
102
|
+
end
|
109
103
|
|
110
|
-
|
111
|
-
read(source)
|
112
|
-
|
113
|
-
|
114
|
-
end
|
104
|
+
# @api private
|
105
|
+
def read(source)
|
106
|
+
super if source.is_a?(::Hash)
|
107
|
+
end
|
115
108
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
115
|
+
# @api private
|
116
|
+
def coercible(&coercer)
|
117
|
+
new(coercer: coercer, members: members.coercible(&coercer))
|
118
|
+
end
|
125
119
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
120
|
+
# @api private
|
121
|
+
def stringified
|
122
|
+
new(name: name.to_s, members: members.stringified)
|
123
|
+
end
|
130
124
|
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
142
|
+
attr_reader :member
|
144
143
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
144
|
+
# @api private
|
145
|
+
def initialize(id, member:, **opts)
|
146
|
+
super(id, **opts)
|
147
|
+
@member = member
|
148
|
+
end
|
150
149
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
162
|
+
# @api private
|
163
|
+
def coercible(&coercer)
|
164
|
+
new(coercer: coercer, member: member.coercible(&coercer))
|
165
|
+
end
|
166
166
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
167
|
+
# @api private
|
168
|
+
def stringified
|
169
|
+
new(name: name.to_s, member: member.stringified)
|
170
|
+
end
|
171
171
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
178
|
-
|
179
|
-
|
177
|
+
# @api private
|
178
|
+
def dump
|
179
|
+
[name, member.dump]
|
180
|
+
end
|
180
181
|
end
|
181
182
|
end
|
182
183
|
end
|