dry-schema 1.2.0 → 1.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +214 -100
- data/README.md +4 -2
- data/config/errors.yml +9 -0
- data/lib/dry/schema/constants.rb +3 -2
- data/lib/dry/schema/dsl.rb +64 -17
- data/lib/dry/schema/extensions/hints/message_set_methods.rb +48 -1
- data/lib/dry/schema/macros/dsl.rb +22 -11
- data/lib/dry/schema/macros/filled.rb +0 -7
- data/lib/dry/schema/macros/key.rb +0 -1
- data/lib/dry/schema/macros/schema.rb +18 -7
- data/lib/dry/schema/macros/value.rb +21 -2
- data/lib/dry/schema/message.rb +1 -1
- data/lib/dry/schema/message_compiler.rb +2 -1
- data/lib/dry/schema/messages/abstract.rb +42 -12
- data/lib/dry/schema/messages/i18n.rb +9 -0
- data/lib/dry/schema/messages/namespaced.rb +6 -1
- data/lib/dry/schema/messages/yaml.rb +12 -0
- data/lib/dry/schema/namespaced_rule.rb +2 -1
- data/lib/dry/schema/path.rb +10 -6
- data/lib/dry/schema/predicate_inferrer.rb +69 -16
- data/lib/dry/schema/predicate_registry.rb +2 -23
- data/lib/dry/schema/processor.rb +9 -26
- data/lib/dry/schema/processor_steps.rb +116 -0
- data/lib/dry/schema/trace.rb +15 -20
- data/lib/dry/schema/type_registry.rb +2 -2
- data/lib/dry/schema/version.rb +1 -1
- metadata +5 -5
- data/lib/dry/schema/primitive_inferrer.rb +0 -98
data/lib/dry/schema/message.rb
CHANGED
@@ -130,7 +130,8 @@ module Dry
|
|
130
130
|
path: path.last, **tokens, **lookup_options(arg_vals: arg_vals, input: input)
|
131
131
|
).to_h
|
132
132
|
|
133
|
-
template, meta = messages[predicate, options] ||
|
133
|
+
template, meta = messages[predicate, options] ||
|
134
|
+
raise(MissingMessageError.new(path, messages.looked_up_paths(predicate, options)))
|
134
135
|
|
135
136
|
text = message_text(template, tokens, options)
|
136
137
|
|
@@ -92,30 +92,34 @@ module Dry
|
|
92
92
|
#
|
93
93
|
# @api public
|
94
94
|
def call(predicate, options)
|
95
|
-
cache.fetch_or_store(
|
95
|
+
cache.fetch_or_store(cache_key(predicate, options)) do
|
96
96
|
text, meta = lookup(predicate, options)
|
97
97
|
[Template[text], meta] if text
|
98
98
|
end
|
99
99
|
end
|
100
100
|
alias_method :[], :call
|
101
101
|
|
102
|
+
# Retrieve an array of looked up paths
|
103
|
+
#
|
104
|
+
# @param [Symbol] predicate
|
105
|
+
# @param [Hash] options
|
106
|
+
#
|
107
|
+
# @return [String]
|
108
|
+
#
|
109
|
+
# @api public
|
110
|
+
def looked_up_paths(predicate, options)
|
111
|
+
tokens = lookup_tokens(predicate, options)
|
112
|
+
filled_lookup_paths(tokens)
|
113
|
+
end
|
114
|
+
|
102
115
|
# Try to find a message for the given predicate and its options
|
103
116
|
#
|
104
117
|
# @api private
|
105
118
|
#
|
106
119
|
# rubocop:disable Metrics/AbcSize
|
107
120
|
def lookup(predicate, options)
|
108
|
-
tokens = options.merge(
|
109
|
-
predicate: predicate,
|
110
|
-
root: options[:not] ? "#{root}.not" : root,
|
111
|
-
arg_type: config.arg_types[options[:arg_type]],
|
112
|
-
val_type: config.val_types[options[:val_type]],
|
113
|
-
message_type: options[:message_type] || :failure
|
114
|
-
)
|
115
|
-
|
116
121
|
opts = options.reject { |k, _| config.lookup_options.include?(k) }
|
117
|
-
|
118
|
-
path = lookup_paths(tokens).detect { |key| key?(key, opts) }
|
122
|
+
path = lookup_paths(predicate, options).detect { |key| key?(key, opts) }
|
119
123
|
|
120
124
|
return unless path
|
121
125
|
|
@@ -130,7 +134,13 @@ module Dry
|
|
130
134
|
# rubocop:enable Metrics/AbcSize
|
131
135
|
|
132
136
|
# @api private
|
133
|
-
def lookup_paths(
|
137
|
+
def lookup_paths(predicate, options)
|
138
|
+
tokens = lookup_tokens(predicate, options)
|
139
|
+
filled_lookup_paths(tokens)
|
140
|
+
end
|
141
|
+
|
142
|
+
# @api private
|
143
|
+
def filled_lookup_paths(tokens)
|
134
144
|
config.lookup_paths.map { |path| path % tokens }
|
135
145
|
end
|
136
146
|
|
@@ -167,8 +177,28 @@ module Dry
|
|
167
177
|
config.default_locale
|
168
178
|
end
|
169
179
|
|
180
|
+
# @api private
|
181
|
+
def cache_key(predicate, options)
|
182
|
+
if options.key?(:input)
|
183
|
+
[predicate, options.reject { |k,| k.equal?(:input) }]
|
184
|
+
else
|
185
|
+
[predicate, options]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
170
189
|
private
|
171
190
|
|
191
|
+
# @api private
|
192
|
+
def lookup_tokens(predicate, options)
|
193
|
+
options.merge(
|
194
|
+
predicate: predicate,
|
195
|
+
root: options[:not] ? "#{root}.not" : root,
|
196
|
+
arg_type: config.arg_types[options[:arg_type]],
|
197
|
+
val_type: config.val_types[options[:val_type]],
|
198
|
+
message_type: options[:message_type] || :failure
|
199
|
+
)
|
200
|
+
end
|
201
|
+
|
172
202
|
# @api private
|
173
203
|
def custom_top_namespace?(path)
|
174
204
|
path.to_s == DEFAULT_MESSAGES_PATH.to_s && config.top_namespace != DEFAULT_MESSAGES_ROOT
|
@@ -55,7 +55,7 @@ module Dry
|
|
55
55
|
end
|
56
56
|
|
57
57
|
# @api private
|
58
|
-
def
|
58
|
+
def filled_lookup_paths(tokens)
|
59
59
|
super(tokens.merge(root: "#{tokens[:root]}.#{namespace}")) + super
|
60
60
|
end
|
61
61
|
|
@@ -64,6 +64,11 @@ module Dry
|
|
64
64
|
base_paths = messages.rule_lookup_paths(tokens)
|
65
65
|
base_paths.map { |key| key.gsub('dry_schema', "dry_schema.#{namespace}") } + base_paths
|
66
66
|
end
|
67
|
+
|
68
|
+
# @api private
|
69
|
+
def cache_key(predicate, options)
|
70
|
+
messages.cache_key(predicate, options)
|
71
|
+
end
|
67
72
|
end
|
68
73
|
end
|
69
74
|
end
|
@@ -68,6 +68,18 @@ module Dry
|
|
68
68
|
@t = proc { |key, locale: default_locale| get("%<locale>s.#{key}", locale: locale) }
|
69
69
|
end
|
70
70
|
|
71
|
+
# Get an array of looked up paths
|
72
|
+
#
|
73
|
+
# @param [Symbol] predicate
|
74
|
+
# @param [Hash] options
|
75
|
+
#
|
76
|
+
# @return [String]
|
77
|
+
#
|
78
|
+
# @api public
|
79
|
+
def looked_up_paths(predicate, options)
|
80
|
+
super.map { |path| path % { locale: options[:locale] || default_locale } }
|
81
|
+
end
|
82
|
+
|
71
83
|
# Get a message for the given key and its options
|
72
84
|
#
|
73
85
|
# @param [Symbol] key
|
data/lib/dry/schema/path.rb
CHANGED
@@ -65,8 +65,10 @@ module Dry
|
|
65
65
|
# @api private
|
66
66
|
def include?(other)
|
67
67
|
return false unless same_root?(other)
|
68
|
-
return
|
69
|
-
self
|
68
|
+
return last.equal?(other.last) if index? && other.index?
|
69
|
+
return self.class.new([*to_a[0..-2]]).include?(other) if index?
|
70
|
+
|
71
|
+
self >= other && !other.key_matches(self).include?(nil)
|
70
72
|
end
|
71
73
|
|
72
74
|
# @api private
|
@@ -75,14 +77,16 @@ module Dry
|
|
75
77
|
|
76
78
|
return 0 if keys.eql?(other.keys)
|
77
79
|
|
78
|
-
res =
|
79
|
-
map { |key| (idx = other.index(key)) && keys[idx].equal?(key) }
|
80
|
-
.compact
|
81
|
-
.reject { |value| value.equal?(false) }
|
80
|
+
res = key_matches(other).compact.reject { |value| value.equal?(false) }
|
82
81
|
|
83
82
|
res.size < count ? 1 : -1
|
84
83
|
end
|
85
84
|
|
85
|
+
# @api private
|
86
|
+
def key_matches(other)
|
87
|
+
map { |key| (idx = other.index(key)) && keys[idx].equal?(key) }
|
88
|
+
end
|
89
|
+
|
86
90
|
# @api private
|
87
91
|
def last
|
88
92
|
keys.last
|
@@ -22,9 +22,15 @@ module Dry
|
|
22
22
|
}.freeze
|
23
23
|
|
24
24
|
REDUCED_TYPES = {
|
25
|
-
|
25
|
+
[[[:true?], [:false?]]] => %i[bool?]
|
26
26
|
}.freeze
|
27
27
|
|
28
|
+
HASH = %i[hash?].freeze
|
29
|
+
|
30
|
+
ARRAY = %i[array?].freeze
|
31
|
+
|
32
|
+
NIL = %i[nil?].freeze
|
33
|
+
|
28
34
|
# Compiler reduces type AST into a list of predicates
|
29
35
|
#
|
30
36
|
# @api private
|
@@ -40,7 +46,7 @@ module Dry
|
|
40
46
|
|
41
47
|
# @api private
|
42
48
|
def infer_predicate(type)
|
43
|
-
TYPE_TO_PREDICATE.fetch(type) { :"#{type.name.split('::').last.downcase}?" }
|
49
|
+
[TYPE_TO_PREDICATE.fetch(type) { :"#{type.name.split('::').last.downcase}?" }]
|
44
50
|
end
|
45
51
|
|
46
52
|
# @api private
|
@@ -54,21 +60,21 @@ module Dry
|
|
54
60
|
type = node[0]
|
55
61
|
predicate = infer_predicate(type)
|
56
62
|
|
57
|
-
if registry.key?(predicate)
|
63
|
+
if registry.key?(predicate[0])
|
58
64
|
predicate
|
59
65
|
else
|
60
|
-
|
66
|
+
[type?: type]
|
61
67
|
end
|
62
68
|
end
|
63
69
|
|
64
70
|
# @api private
|
65
71
|
def visit_hash(_)
|
66
|
-
|
72
|
+
HASH
|
67
73
|
end
|
68
74
|
|
69
75
|
# @api private
|
70
76
|
def visit_array(_)
|
71
|
-
|
77
|
+
ARRAY
|
72
78
|
end
|
73
79
|
|
74
80
|
# @api private
|
@@ -90,26 +96,73 @@ module Dry
|
|
90
96
|
|
91
97
|
# @api private
|
92
98
|
def visit_sum(node)
|
93
|
-
|
99
|
+
left_node, right_node, = node
|
100
|
+
left = visit(left_node)
|
101
|
+
right = visit(right_node)
|
94
102
|
|
95
|
-
|
96
|
-
|
97
|
-
if predicates.first == :nil?
|
98
|
-
predicates[1..predicates.size - 1]
|
103
|
+
if left.eql?(NIL)
|
104
|
+
right
|
99
105
|
else
|
100
|
-
|
106
|
+
[[left, right]]
|
101
107
|
end
|
102
108
|
end
|
103
109
|
|
104
110
|
# @api private
|
105
111
|
def visit_constrained(node)
|
106
|
-
other,
|
107
|
-
visit(
|
112
|
+
other, rules = node
|
113
|
+
predicates = visit(rules)
|
114
|
+
|
115
|
+
if predicates.empty?
|
116
|
+
visit(other)
|
117
|
+
else
|
118
|
+
[*visit(other), *merge_predicates(predicates)]
|
119
|
+
end
|
108
120
|
end
|
109
121
|
|
110
122
|
# @api private
|
111
123
|
def visit_any(_)
|
112
|
-
|
124
|
+
EMPTY_ARRAY
|
125
|
+
end
|
126
|
+
|
127
|
+
# @api private
|
128
|
+
def visit_and(node)
|
129
|
+
left, right = node
|
130
|
+
visit(left) + visit(right)
|
131
|
+
end
|
132
|
+
|
133
|
+
# @api private
|
134
|
+
def visit_predicate(node)
|
135
|
+
pred, args = node
|
136
|
+
|
137
|
+
if pred.equal?(:type?)
|
138
|
+
EMPTY_ARRAY
|
139
|
+
elsif registry.key?(pred)
|
140
|
+
*curried, _ = args
|
141
|
+
values = curried.map { |_, v| v }
|
142
|
+
|
143
|
+
if values.empty?
|
144
|
+
[pred]
|
145
|
+
else
|
146
|
+
[pred => values[0]]
|
147
|
+
end
|
148
|
+
else
|
149
|
+
EMPTY_ARRAY
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
# @api private
|
156
|
+
def merge_predicates(nodes)
|
157
|
+
preds, merged = nodes.each_with_object([[], {}]) do |predicate, (ps, h)|
|
158
|
+
if predicate.is_a?(::Hash)
|
159
|
+
h.update(predicate)
|
160
|
+
else
|
161
|
+
ps << predicate
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
merged.empty? ? preds : [*preds, merged]
|
113
166
|
end
|
114
167
|
end
|
115
168
|
|
@@ -134,7 +187,7 @@ module Dry
|
|
134
187
|
if predicates.is_a?(Hash)
|
135
188
|
predicates
|
136
189
|
else
|
137
|
-
|
190
|
+
REDUCED_TYPES[predicates] || predicates
|
138
191
|
end
|
139
192
|
end
|
140
193
|
end
|
@@ -1,35 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'dry/logic/predicates'
|
4
|
+
require 'dry/types/predicate_registry'
|
4
5
|
|
5
6
|
module Dry
|
6
7
|
module Schema
|
7
8
|
# A registry with predicate objects from `Dry::Logic::Predicates`
|
8
9
|
#
|
9
10
|
# @api private
|
10
|
-
class PredicateRegistry
|
11
|
-
# @api private
|
12
|
-
attr_reader :predicates
|
13
|
-
|
14
|
-
# @api private
|
15
|
-
attr_reader :has_predicate
|
16
|
-
|
17
|
-
# @api private
|
18
|
-
def initialize(predicates = Dry::Logic::Predicates)
|
19
|
-
@predicates = predicates
|
20
|
-
@has_predicate = ::Kernel.instance_method(:respond_to?).bind(@predicates)
|
21
|
-
end
|
22
|
-
|
23
|
-
# @api private
|
24
|
-
def [](name)
|
25
|
-
predicates[name]
|
26
|
-
end
|
27
|
-
|
28
|
-
# @api private
|
29
|
-
def key?(name)
|
30
|
-
has_predicate.(name)
|
31
|
-
end
|
32
|
-
|
11
|
+
class PredicateRegistry < Dry::Types::PredicateRegistry
|
33
12
|
# @api private
|
34
13
|
def arg_list(name, *values)
|
35
14
|
predicate = self[name]
|
data/lib/dry/schema/processor.rb
CHANGED
@@ -5,6 +5,7 @@ require 'dry/initializer'
|
|
5
5
|
|
6
6
|
require 'dry/schema/type_registry'
|
7
7
|
require 'dry/schema/type_container'
|
8
|
+
require 'dry/schema/processor_steps'
|
8
9
|
require 'dry/schema/rule_applier'
|
9
10
|
require 'dry/schema/key_coercer'
|
10
11
|
require 'dry/schema/value_coercer'
|
@@ -12,14 +13,9 @@ require 'dry/schema/value_coercer'
|
|
12
13
|
module Dry
|
13
14
|
module Schema
|
14
15
|
# Processes input data using objects configured within the DSL
|
16
|
+
# Processing is split into steps represented by `ProcessorSteps`.
|
15
17
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# 1. Prepare input hash using a key map
|
19
|
-
# 2. Apply pre-coercion filtering rules (optional step, used only when `filter` was used)
|
20
|
-
# 3. Apply value coercions based on type specifications
|
21
|
-
# 4. Apply rules
|
22
|
-
#
|
18
|
+
# @see ProcessorSteps
|
23
19
|
# @see Params
|
24
20
|
# @see JSON
|
25
21
|
#
|
@@ -29,10 +25,10 @@ module Dry
|
|
29
25
|
extend Dry::Configurable
|
30
26
|
|
31
27
|
setting :key_map_type
|
32
|
-
setting :type_registry_namespace, :
|
28
|
+
setting :type_registry_namespace, :strict
|
33
29
|
setting :filter_empty_string, false
|
34
30
|
|
35
|
-
option :steps, default: -> {
|
31
|
+
option :steps, default: -> { ProcessorSteps.new }
|
36
32
|
|
37
33
|
option :schema_dsl
|
38
34
|
|
@@ -77,16 +73,6 @@ module Dry
|
|
77
73
|
end
|
78
74
|
end
|
79
75
|
|
80
|
-
# Append a step
|
81
|
-
#
|
82
|
-
# @return [Processor]
|
83
|
-
#
|
84
|
-
# @api private
|
85
|
-
def <<(step)
|
86
|
-
steps << step
|
87
|
-
self
|
88
|
-
end
|
89
|
-
|
90
76
|
# Apply processing steps to the provided input
|
91
77
|
#
|
92
78
|
# @param [Hash] input
|
@@ -96,10 +82,7 @@ module Dry
|
|
96
82
|
# @api public
|
97
83
|
def call(input)
|
98
84
|
Result.new(input, message_compiler: message_compiler) do |result|
|
99
|
-
steps.
|
100
|
-
output = step.(result)
|
101
|
-
result.replace(output) if output.is_a?(::Hash)
|
102
|
-
end
|
85
|
+
steps.call(result)
|
103
86
|
end
|
104
87
|
end
|
105
88
|
alias_method :[], :call
|
@@ -119,7 +102,7 @@ module Dry
|
|
119
102
|
#
|
120
103
|
# @api public
|
121
104
|
def key_map
|
122
|
-
|
105
|
+
steps[:key_coercer].key_map
|
123
106
|
end
|
124
107
|
|
125
108
|
# Return string represntation
|
@@ -139,7 +122,7 @@ module Dry
|
|
139
122
|
#
|
140
123
|
# @api private
|
141
124
|
def type_schema
|
142
|
-
|
125
|
+
steps[:value_coercer].type_schema
|
143
126
|
end
|
144
127
|
|
145
128
|
# Return the rules config
|
@@ -180,7 +163,7 @@ module Dry
|
|
180
163
|
#
|
181
164
|
# @api private
|
182
165
|
def rule_applier
|
183
|
-
|
166
|
+
steps[:rule_applier]
|
184
167
|
end
|
185
168
|
alias_method :to_rule, :rule_applier
|
186
169
|
|