dry-schema 1.4.3 → 1.5.4
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 +219 -97
- data/config/errors.yml +4 -0
- data/dry-schema.gemspec +46 -0
- data/lib/dry-schema.rb +1 -1
- data/lib/dry/schema.rb +19 -6
- data/lib/dry/schema/compiler.rb +5 -5
- data/lib/dry/schema/config.rb +15 -6
- data/lib/dry/schema/constants.rb +16 -7
- data/lib/dry/schema/dsl.rb +87 -27
- data/lib/dry/schema/extensions.rb +10 -2
- data/lib/dry/schema/extensions/hints.rb +15 -8
- data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +1 -1
- data/lib/dry/schema/extensions/hints/message_set_methods.rb +0 -47
- data/lib/dry/schema/extensions/info.rb +27 -0
- data/lib/dry/schema/extensions/info/schema_compiler.rb +105 -0
- data/lib/dry/schema/extensions/monads.rb +1 -1
- data/lib/dry/schema/extensions/struct.rb +32 -0
- data/lib/dry/schema/json.rb +1 -1
- data/lib/dry/schema/key.rb +16 -1
- data/lib/dry/schema/key_coercer.rb +4 -4
- data/lib/dry/schema/key_map.rb +9 -4
- data/lib/dry/schema/key_validator.rb +67 -0
- data/lib/dry/schema/macros.rb +8 -8
- data/lib/dry/schema/macros/array.rb +17 -4
- data/lib/dry/schema/macros/core.rb +9 -4
- data/lib/dry/schema/macros/dsl.rb +34 -19
- data/lib/dry/schema/macros/each.rb +4 -4
- data/lib/dry/schema/macros/filled.rb +5 -5
- data/lib/dry/schema/macros/hash.rb +21 -3
- data/lib/dry/schema/macros/key.rb +9 -9
- data/lib/dry/schema/macros/maybe.rb +3 -3
- data/lib/dry/schema/macros/optional.rb +1 -1
- data/lib/dry/schema/macros/required.rb +1 -1
- data/lib/dry/schema/macros/schema.rb +23 -2
- data/lib/dry/schema/macros/value.rb +32 -10
- data/lib/dry/schema/message.rb +35 -9
- data/lib/dry/schema/message/or.rb +18 -39
- data/lib/dry/schema/message/or/abstract.rb +28 -0
- data/lib/dry/schema/message/or/multi_path.rb +37 -0
- data/lib/dry/schema/message/or/single_path.rb +64 -0
- data/lib/dry/schema/message_compiler.rb +55 -19
- data/lib/dry/schema/message_compiler/visitor_opts.rb +2 -2
- data/lib/dry/schema/message_set.rb +25 -36
- data/lib/dry/schema/messages.rb +6 -6
- data/lib/dry/schema/messages/abstract.rb +54 -56
- data/lib/dry/schema/messages/i18n.rb +29 -27
- data/lib/dry/schema/messages/namespaced.rb +12 -2
- data/lib/dry/schema/messages/template.rb +19 -44
- data/lib/dry/schema/messages/yaml.rb +60 -13
- data/lib/dry/schema/params.rb +1 -1
- data/lib/dry/schema/path.rb +44 -5
- data/lib/dry/schema/predicate.rb +4 -2
- data/lib/dry/schema/predicate_inferrer.rb +4 -184
- data/lib/dry/schema/predicate_registry.rb +2 -2
- data/lib/dry/schema/primitive_inferrer.rb +16 -0
- data/lib/dry/schema/processor.rb +49 -28
- data/lib/dry/schema/processor_steps.rb +50 -27
- data/lib/dry/schema/result.rb +44 -6
- data/lib/dry/schema/rule_applier.rb +7 -7
- data/lib/dry/schema/step.rb +79 -0
- data/lib/dry/schema/trace.rb +5 -4
- data/lib/dry/schema/type_container.rb +3 -3
- data/lib/dry/schema/type_registry.rb +2 -2
- data/lib/dry/schema/types.rb +1 -1
- data/lib/dry/schema/value_coercer.rb +2 -2
- data/lib/dry/schema/version.rb +1 -1
- metadata +22 -8
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "dry/schema/message/or/single_path"
|
4
|
+
require "dry/schema/message/or/multi_path"
|
4
5
|
|
5
6
|
module Dry
|
6
7
|
module Schema
|
@@ -8,45 +9,23 @@ module Dry
|
|
8
9
|
#
|
9
10
|
# @api public
|
10
11
|
class Message
|
11
|
-
|
12
|
-
#
|
13
|
-
# @api public
|
14
|
-
class Or
|
12
|
+
module Or
|
15
13
|
# @api private
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
@messages = messages
|
32
|
-
@path = left.path
|
33
|
-
end
|
34
|
-
|
35
|
-
# Dump a message into a string
|
36
|
-
#
|
37
|
-
# @see Message#dump
|
38
|
-
#
|
39
|
-
# @return [String]
|
40
|
-
#
|
41
|
-
# @api public
|
42
|
-
def dump
|
43
|
-
to_a.map(&:dump).join(" #{messages[:or][:text]} ")
|
44
|
-
end
|
45
|
-
alias to_s dump
|
46
|
-
|
47
|
-
# @api private
|
48
|
-
def to_a
|
49
|
-
[left, right]
|
14
|
+
def self.[](left, right, messages)
|
15
|
+
msgs = [left, right].flatten
|
16
|
+
paths = msgs.map(&:path)
|
17
|
+
|
18
|
+
if paths.uniq.size == 1
|
19
|
+
SinglePath.new(left, right, messages)
|
20
|
+
elsif right.is_a?(Array)
|
21
|
+
if left.is_a?(Array) && paths.uniq.size > 1
|
22
|
+
MultiPath.new(left, right)
|
23
|
+
else
|
24
|
+
right
|
25
|
+
end
|
26
|
+
else
|
27
|
+
msgs.max
|
28
|
+
end
|
50
29
|
end
|
51
30
|
end
|
52
31
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Schema
|
5
|
+
class Message
|
6
|
+
module Or
|
7
|
+
# A message type used by OR operations
|
8
|
+
#
|
9
|
+
# @abstract
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
class Abstract
|
13
|
+
# @api private
|
14
|
+
attr_reader :left
|
15
|
+
|
16
|
+
# @api private
|
17
|
+
attr_reader :right
|
18
|
+
|
19
|
+
# @api private
|
20
|
+
def initialize(left, right)
|
21
|
+
@left = left
|
22
|
+
@right = right
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/equalizer"
|
4
|
+
|
5
|
+
require "dry/schema/message/or/abstract"
|
6
|
+
require "dry/schema/path"
|
7
|
+
|
8
|
+
module Dry
|
9
|
+
module Schema
|
10
|
+
class Message
|
11
|
+
module Or
|
12
|
+
# A message type used by OR operations with different paths
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
class MultiPath < Abstract
|
16
|
+
# @api private
|
17
|
+
attr_reader :root
|
18
|
+
|
19
|
+
# @api private
|
20
|
+
def initialize(*args)
|
21
|
+
super
|
22
|
+
@root = [left, right].flatten.map(&:_path).reduce(:&)
|
23
|
+
@left = left.map { |msg| msg.to_or(root) }
|
24
|
+
@right = right.map { |msg| msg.to_or(root) }
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api public
|
28
|
+
def to_h
|
29
|
+
@to_h ||= Path[[*root, :or]].to_h(
|
30
|
+
[left.map(&:to_h).reduce(:merge), right.map(&:to_h).reduce(:merge)]
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/schema/message/or/abstract"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Schema
|
7
|
+
class Message
|
8
|
+
module Or
|
9
|
+
# A message type used by OR operations with the same path
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
class SinglePath < Abstract
|
13
|
+
# @api private
|
14
|
+
attr_reader :path
|
15
|
+
|
16
|
+
# @api private
|
17
|
+
attr_reader :_path
|
18
|
+
|
19
|
+
# @api private
|
20
|
+
attr_reader :messages
|
21
|
+
|
22
|
+
# @api private
|
23
|
+
def initialize(*args, messages)
|
24
|
+
super(*args)
|
25
|
+
@messages = messages
|
26
|
+
@path = left.path
|
27
|
+
@_path = left._path
|
28
|
+
end
|
29
|
+
|
30
|
+
# Dump a message into a string
|
31
|
+
#
|
32
|
+
# Both sides of the message will be joined using translated
|
33
|
+
# value under `dry_schema.or` message key
|
34
|
+
#
|
35
|
+
# @see Message#dump
|
36
|
+
#
|
37
|
+
# @return [String]
|
38
|
+
#
|
39
|
+
# @api public
|
40
|
+
def dump
|
41
|
+
@dump ||= "#{left.dump} #{messages[:or][:text]} #{right.dump}"
|
42
|
+
end
|
43
|
+
alias_method :to_s, :dump
|
44
|
+
|
45
|
+
# Dump an `or` message into a hash
|
46
|
+
#
|
47
|
+
# @see Message#to_h
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
#
|
51
|
+
# @api public
|
52
|
+
def to_h
|
53
|
+
@to_h ||= _path.to_h(dump)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @api private
|
57
|
+
def to_a
|
58
|
+
@to_a ||= [left, right]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "dry/initializer"
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
5
|
+
require "dry/schema/constants"
|
6
|
+
require "dry/schema/message"
|
7
|
+
require "dry/schema/message_set"
|
8
|
+
require "dry/schema/message_compiler/visitor_opts"
|
9
9
|
|
10
10
|
module Dry
|
11
11
|
module Schema
|
@@ -29,6 +29,14 @@ module Dry
|
|
29
29
|
|
30
30
|
EMPTY_OPTS = VisitorOpts.new
|
31
31
|
EMPTY_MESSAGE_SET = MessageSet.new(EMPTY_ARRAY).freeze
|
32
|
+
FULL_MESSAGE_WHITESPACE = Hash.new(' ').merge(
|
33
|
+
ja: '',
|
34
|
+
zh: '',
|
35
|
+
bn: '',
|
36
|
+
th: '',
|
37
|
+
lo: '',
|
38
|
+
my: '',
|
39
|
+
)
|
32
40
|
|
33
41
|
param :messages
|
34
42
|
|
@@ -44,7 +52,7 @@ module Dry
|
|
44
52
|
def initialize(messages, **options)
|
45
53
|
super
|
46
54
|
@options = options
|
47
|
-
@default_lookup_options = options[:locale] ? {
|
55
|
+
@default_lookup_options = options[:locale] ? {locale: locale} : EMPTY_HASH
|
48
56
|
end
|
49
57
|
|
50
58
|
# @api private
|
@@ -100,17 +108,29 @@ module Dry
|
|
100
108
|
end
|
101
109
|
end
|
102
110
|
|
111
|
+
# @api private
|
112
|
+
def visit_unexpected_key(node, _opts)
|
113
|
+
path, input = node
|
114
|
+
|
115
|
+
msg = messages.translate("errors.unexpected_key")
|
116
|
+
|
117
|
+
Message.new(
|
118
|
+
path: path,
|
119
|
+
text: msg[:text],
|
120
|
+
predicate: nil,
|
121
|
+
input: input
|
122
|
+
)
|
123
|
+
end
|
124
|
+
|
103
125
|
# @api private
|
104
126
|
def visit_or(node, opts)
|
105
127
|
left, right = node.map { |n| visit(n, opts) }
|
128
|
+
Message::Or[left, right, or_translator]
|
129
|
+
end
|
106
130
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
right
|
111
|
-
else
|
112
|
-
[left, right].flatten.max
|
113
|
-
end
|
131
|
+
# @api private
|
132
|
+
def or_translator
|
133
|
+
@or_translator ||= proc { |k| messages.translate(k, **default_lookup_options) }
|
114
134
|
end
|
115
135
|
|
116
136
|
# @api private
|
@@ -130,13 +150,21 @@ module Dry
|
|
130
150
|
path: path.last, **tokens, **lookup_options(arg_vals: arg_vals, input: input)
|
131
151
|
).to_h
|
132
152
|
|
133
|
-
template, meta = messages[predicate, options]
|
134
|
-
|
153
|
+
template, meta = messages[predicate, options]
|
154
|
+
|
155
|
+
unless template
|
156
|
+
raise MissingMessageError.new(path, messages.looked_up_paths(predicate, options))
|
157
|
+
end
|
135
158
|
|
136
159
|
text = message_text(template, tokens, options)
|
137
160
|
|
138
161
|
message_type(options).new(
|
139
|
-
text: text,
|
162
|
+
text: text,
|
163
|
+
meta: meta,
|
164
|
+
path: path,
|
165
|
+
predicate: predicate,
|
166
|
+
args: arg_vals,
|
167
|
+
input: input
|
140
168
|
)
|
141
169
|
end
|
142
170
|
|
@@ -180,15 +208,15 @@ module Dry
|
|
180
208
|
def message_text(template, tokens, options)
|
181
209
|
text = template[template.data(tokens)]
|
182
210
|
|
183
|
-
return text
|
211
|
+
return text if !text || !full
|
184
212
|
|
185
213
|
rule = options[:path]
|
186
|
-
|
214
|
+
[messages.rule(rule, options) || rule, text].join(FULL_MESSAGE_WHITESPACE[template.options[:locale]])
|
187
215
|
end
|
188
216
|
|
189
217
|
# @api private
|
190
218
|
def message_tokens(args)
|
191
|
-
args.each_with_object({}) do |arg, hash|
|
219
|
+
tokens = args.each_with_object({}) do |arg, hash|
|
192
220
|
case arg[1]
|
193
221
|
when Array
|
194
222
|
hash[arg[0]] = arg[1].join(LIST_SEPARATOR)
|
@@ -199,6 +227,14 @@ module Dry
|
|
199
227
|
hash[arg[0]] = arg[1]
|
200
228
|
end
|
201
229
|
end
|
230
|
+
args.any? { |e| e.first == :size } ? append_mapped_size_tokens(tokens) : tokens
|
231
|
+
end
|
232
|
+
|
233
|
+
# @api private
|
234
|
+
def append_mapped_size_tokens(tokens)
|
235
|
+
# this is a temporary fix for the inconsistency in the "size" errors arguments
|
236
|
+
mapped_hash = tokens.each_with_object({}) { |(k, v), h| h[k.to_s.gsub("size", "num").to_sym] = v }
|
237
|
+
tokens.merge(mapped_hash)
|
202
238
|
end
|
203
239
|
end
|
204
240
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "dry/equalizer"
|
4
4
|
|
5
5
|
module Dry
|
6
6
|
module Schema
|
@@ -18,12 +18,6 @@ module Dry
|
|
18
18
|
# @return [Array<Message>]
|
19
19
|
attr_reader :messages
|
20
20
|
|
21
|
-
# An internal hash that is filled in with dumped messages
|
22
|
-
# when a message set is coerced to a hash
|
23
|
-
#
|
24
|
-
# @return [Hash<Symbol=>[Array,Hash]>]
|
25
|
-
attr_reader :placeholders
|
26
|
-
|
27
21
|
# Options hash
|
28
22
|
#
|
29
23
|
# @return [Hash]
|
@@ -38,7 +32,6 @@ module Dry
|
|
38
32
|
def initialize(messages, options = EMPTY_HASH)
|
39
33
|
@messages = messages
|
40
34
|
@options = options
|
41
|
-
initialize_placeholders!
|
42
35
|
end
|
43
36
|
|
44
37
|
# Iterate over messages
|
@@ -112,43 +105,39 @@ module Dry
|
|
112
105
|
|
113
106
|
# @api private
|
114
107
|
def messages_map(messages = self.messages)
|
115
|
-
|
116
|
-
|
117
|
-
messages.group_by(&:path).reduce(placeholders) do |hash, (path, msgs)|
|
118
|
-
node = path.reduce(hash) { |a, e| a[e] }
|
108
|
+
combine_message_hashes(messages.map(&:to_h))
|
109
|
+
end
|
119
110
|
|
120
|
-
|
121
|
-
|
111
|
+
# @api private
|
112
|
+
def combine_message_hashes(hashes)
|
113
|
+
hashes.reduce(EMPTY_HASH.dup) do |a, e|
|
114
|
+
a.merge(e) do |_, *values|
|
115
|
+
combine_message_values(values)
|
122
116
|
end
|
123
|
-
|
124
|
-
node.map!(&:dump)
|
125
|
-
|
126
|
-
hash
|
127
117
|
end
|
128
118
|
end
|
129
119
|
|
130
120
|
# @api private
|
131
|
-
def
|
132
|
-
|
121
|
+
def combine_message_values(values)
|
122
|
+
hashes, other = partition_message_values(values)
|
123
|
+
combined = combine_message_hashes(hashes)
|
124
|
+
flattened = other.flatten
|
125
|
+
|
126
|
+
if flattened.empty?
|
127
|
+
combined
|
128
|
+
elsif combined.empty?
|
129
|
+
flattened
|
130
|
+
else
|
131
|
+
[flattened, combined]
|
132
|
+
end
|
133
133
|
end
|
134
134
|
|
135
135
|
# @api private
|
136
|
-
def
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
last_idx = path.size - 1
|
142
|
-
node = hash
|
143
|
-
|
144
|
-
while curr_idx <= last_idx
|
145
|
-
key = path[curr_idx]
|
146
|
-
node = (node[key] || node[key] = curr_idx < last_idx ? {} : [])
|
147
|
-
curr_idx += 1
|
148
|
-
end
|
149
|
-
|
150
|
-
hash
|
151
|
-
end
|
136
|
+
def partition_message_values(values)
|
137
|
+
values
|
138
|
+
.map { |value| value.is_a?(Array) ? value : [value] }
|
139
|
+
.reduce(EMPTY_ARRAY.dup, :+)
|
140
|
+
.partition { |value| value.is_a?(Hash) && !value[:text].is_a?(String) }
|
152
141
|
end
|
153
142
|
end
|
154
143
|
end
|
data/lib/dry/schema/messages.rb
CHANGED
@@ -7,8 +7,8 @@ module Dry
|
|
7
7
|
# @api private
|
8
8
|
module Messages
|
9
9
|
BACKENDS = {
|
10
|
-
i18n:
|
11
|
-
yaml:
|
10
|
+
i18n: "I18n",
|
11
|
+
yaml: "YAML"
|
12
12
|
}.freeze
|
13
13
|
|
14
14
|
module_function
|
@@ -31,7 +31,7 @@ module Dry
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
require
|
35
|
-
require
|
36
|
-
require
|
37
|
-
require
|
34
|
+
require "dry/schema/messages/abstract"
|
35
|
+
require "dry/schema/messages/namespaced"
|
36
|
+
require "dry/schema/messages/yaml"
|
37
|
+
require "dry/schema/messages/i18n" if defined?(I18n)
|