dry-validation 0.3.1 → 0.4.0
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 +16 -0
- data/README.md +35 -499
- data/lib/dry/validation/error_compiler.rb +9 -4
- data/lib/dry/validation/hint_compiler.rb +69 -0
- data/lib/dry/validation/predicate.rb +0 -4
- data/lib/dry/validation/result.rb +8 -0
- data/lib/dry/validation/rule.rb +44 -0
- data/lib/dry/validation/rule/check.rb +15 -0
- data/lib/dry/validation/rule/composite.rb +20 -7
- data/lib/dry/validation/rule/result.rb +46 -0
- data/lib/dry/validation/rule_compiler.rb +14 -0
- data/lib/dry/validation/schema.rb +33 -3
- data/lib/dry/validation/schema/definition.rb +25 -4
- data/lib/dry/validation/schema/key.rb +8 -8
- data/lib/dry/validation/schema/result.rb +15 -2
- data/lib/dry/validation/schema/rule.rb +32 -5
- data/lib/dry/validation/schema/value.rb +15 -6
- data/lib/dry/validation/version.rb +1 -1
- data/spec/integration/custom_error_messages_spec.rb +1 -1
- data/spec/integration/error_compiler_spec.rb +30 -56
- data/spec/integration/hints_spec.rb +39 -0
- data/spec/integration/localized_error_messages_spec.rb +2 -2
- data/spec/integration/schema/check_rules_spec.rb +28 -0
- data/spec/integration/schema/each_with_set_spec.rb +71 -0
- data/spec/integration/schema/nested_spec.rb +31 -0
- data/spec/integration/schema/not_spec.rb +34 -0
- data/spec/integration/schema/xor_spec.rb +32 -0
- data/spec/integration/schema_form_spec.rb +2 -2
- data/spec/integration/schema_spec.rb +1 -1
- data/spec/shared/predicates.rb +2 -0
- data/spec/spec_helper.rb +2 -2
- data/spec/unit/hint_compiler_spec.rb +32 -0
- data/spec/unit/predicate_spec.rb +0 -10
- data/spec/unit/rule/check_spec.rb +29 -0
- data/spec/unit/rule_compiler_spec.rb +44 -7
- data/spec/unit/schema/rule_spec.rb +31 -0
- data/spec/unit/schema/value_spec.rb +84 -0
- metadata +24 -2
@@ -2,6 +2,13 @@ module Dry
|
|
2
2
|
module Validation
|
3
3
|
class Schema
|
4
4
|
module Definition
|
5
|
+
def schema(name, &block)
|
6
|
+
schema = Class.new(superclass)
|
7
|
+
schema.key(name, &block)
|
8
|
+
schemas << schema
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
5
12
|
def key(name, &block)
|
6
13
|
Key.new(name, rules).key?(&block)
|
7
14
|
end
|
@@ -10,11 +17,19 @@ module Dry
|
|
10
17
|
Key.new(name, rules).optional(&block)
|
11
18
|
end
|
12
19
|
|
13
|
-
def rule(name, **options)
|
14
|
-
|
15
|
-
|
20
|
+
def rule(name, **options, &block)
|
21
|
+
if options.any?
|
22
|
+
predicate, rule_names = options.to_a.first
|
23
|
+
identifier = { name => rule_names }
|
16
24
|
|
17
|
-
|
25
|
+
groups << [:group, [identifier, [:predicate, predicate]]]
|
26
|
+
else
|
27
|
+
if block
|
28
|
+
checks << Schema::Rule.new(name, [:check, [name, yield.to_ary]])
|
29
|
+
else
|
30
|
+
rule_by_name(name).to_check
|
31
|
+
end
|
32
|
+
end
|
18
33
|
end
|
19
34
|
|
20
35
|
def confirmation(name)
|
@@ -25,6 +40,12 @@ module Dry
|
|
25
40
|
|
26
41
|
rule(identifier, eql?: [name, identifier])
|
27
42
|
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def rule_by_name(name)
|
47
|
+
rules.detect { |rule| rule.name == name }
|
48
|
+
end
|
28
49
|
end
|
29
50
|
end
|
30
51
|
end
|
@@ -3,7 +3,7 @@ require 'dry/validation/rule'
|
|
3
3
|
module Dry
|
4
4
|
module Validation
|
5
5
|
class Schema
|
6
|
-
class Key
|
6
|
+
class Key < BasicObject
|
7
7
|
attr_reader :name, :rules
|
8
8
|
|
9
9
|
def initialize(name, rules, &block)
|
@@ -17,10 +17,10 @@ module Dry
|
|
17
17
|
val_rule = yield(Value.new(name))
|
18
18
|
|
19
19
|
rules <<
|
20
|
-
if val_rule.is_a?(Array)
|
21
|
-
Schema::Rule.new([:implication, [key_rule.to_ary, [:set, [name, val_rule.map(&:to_ary)]]]])
|
20
|
+
if val_rule.is_a?(::Array)
|
21
|
+
Schema::Rule.new(name, [:implication, [key_rule.to_ary, [:set, [name, val_rule.map(&:to_ary)]]]])
|
22
22
|
else
|
23
|
-
Schema::Rule.new([:implication, [key_rule.to_ary, val_rule.to_ary]])
|
23
|
+
Schema::Rule.new(name, [:implication, [key_rule.to_ary, val_rule.to_ary]])
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -33,13 +33,13 @@ module Dry
|
|
33
33
|
val_rule = yield(Value.new(name))
|
34
34
|
|
35
35
|
rules <<
|
36
|
-
if val_rule.is_a?(Array)
|
37
|
-
Schema::Rule.new([:and, [key_rule, [:set, [name, val_rule.map(&:to_ary)]]]])
|
36
|
+
if val_rule.is_a?(::Array)
|
37
|
+
Schema::Rule.new(name, [:and, [key_rule, [:set, [name, val_rule.map(&:to_ary)]]]])
|
38
38
|
else
|
39
|
-
Schema::Rule.new([:and, [key_rule, val_rule.to_ary]])
|
39
|
+
Schema::Rule.new(name, [:and, [key_rule, val_rule.to_ary]])
|
40
40
|
end
|
41
41
|
else
|
42
|
-
Schema::Rule.new(key_rule)
|
42
|
+
Schema::Rule.new(name, key_rule)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
@@ -12,11 +12,14 @@ module Dry
|
|
12
12
|
|
13
13
|
attr_reader :error_compiler
|
14
14
|
|
15
|
-
|
15
|
+
attr_reader :hint_compiler
|
16
|
+
|
17
|
+
def initialize(params, result, errors, error_compiler, hint_compiler)
|
16
18
|
@params = params
|
17
19
|
@result = result
|
18
20
|
@errors = errors
|
19
21
|
@error_compiler = error_compiler
|
22
|
+
@hint_compiler = hint_compiler
|
20
23
|
end
|
21
24
|
|
22
25
|
def each(&block)
|
@@ -32,7 +35,17 @@ module Dry
|
|
32
35
|
end
|
33
36
|
|
34
37
|
def messages(options = {})
|
35
|
-
@messages ||=
|
38
|
+
@messages ||=
|
39
|
+
begin
|
40
|
+
all_msgs = error_compiler.with(options).(errors.map(&:to_ary))
|
41
|
+
hints = hint_compiler.with(options).call
|
42
|
+
|
43
|
+
all_msgs.each do |(name, data)|
|
44
|
+
data[0].concat(hints[name]).uniq!
|
45
|
+
end
|
46
|
+
|
47
|
+
all_msgs
|
48
|
+
end
|
36
49
|
end
|
37
50
|
|
38
51
|
def successes
|
@@ -4,7 +4,14 @@ module Dry
|
|
4
4
|
class Rule
|
5
5
|
attr_reader :name, :node
|
6
6
|
|
7
|
-
|
7
|
+
class Check < Rule
|
8
|
+
def method_missing(meth, *)
|
9
|
+
self.class.new(name, [:check, [name, [:predicate, [name, [meth]]]]])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(name, node)
|
14
|
+
@name = name
|
8
15
|
@node = node
|
9
16
|
end
|
10
17
|
|
@@ -13,13 +20,33 @@ module Dry
|
|
13
20
|
end
|
14
21
|
alias_method :to_a, :to_ary
|
15
22
|
|
16
|
-
def
|
17
|
-
|
23
|
+
def to_check
|
24
|
+
Rule::Check.new(name, [:check, [name, [:predicate, [name, []]]]])
|
25
|
+
end
|
26
|
+
|
27
|
+
def not
|
28
|
+
self.class.new(:"not_#{name}", [:not, node])
|
29
|
+
end
|
30
|
+
|
31
|
+
def and(other)
|
32
|
+
self.class.new(:"#{name}_and_#{other.name}", [:and, [node, other.to_ary]])
|
33
|
+
end
|
34
|
+
alias_method :&, :and
|
35
|
+
|
36
|
+
def or(other)
|
37
|
+
self.class.new(:"#{name}_or_#{other.name}", [:or, [node, other.to_ary]])
|
38
|
+
end
|
39
|
+
alias_method :|, :or
|
40
|
+
|
41
|
+
def xor(other)
|
42
|
+
self.class.new(:"#{name}_xor_#{other.name}", [:xor, [node, other.to_ary]])
|
18
43
|
end
|
44
|
+
alias_method :^, :xor
|
19
45
|
|
20
|
-
def
|
21
|
-
self.class.new([:
|
46
|
+
def then(other)
|
47
|
+
self.class.new(:"#{name}_then_#{other.name}", [:implication, [node, other.to_ary]])
|
22
48
|
end
|
49
|
+
alias_method :>, :then
|
23
50
|
end
|
24
51
|
end
|
25
52
|
end
|
@@ -1,26 +1,35 @@
|
|
1
1
|
module Dry
|
2
2
|
module Validation
|
3
3
|
class Schema
|
4
|
-
class Value
|
4
|
+
class Value < BasicObject
|
5
5
|
include Schema::Definition
|
6
6
|
|
7
|
-
attr_reader :name, :rules, :groups
|
7
|
+
attr_reader :name, :rules, :groups, :checks
|
8
8
|
|
9
9
|
def initialize(name)
|
10
10
|
@name = name
|
11
11
|
@rules = []
|
12
12
|
@groups = []
|
13
|
+
@checks = []
|
13
14
|
end
|
14
15
|
|
15
16
|
def each(&block)
|
16
|
-
|
17
|
-
|
17
|
+
val_rule = yield(self)
|
18
|
+
|
19
|
+
each_rule =
|
20
|
+
if val_rule.is_a?(Schema::Rule)
|
21
|
+
val_rule.to_ary
|
22
|
+
else
|
23
|
+
[:set, [name, rules.map(&:to_ary)]]
|
24
|
+
end
|
25
|
+
|
26
|
+
Schema::Rule.new(name, [:each, [name, each_rule]])
|
18
27
|
end
|
19
28
|
|
20
29
|
private
|
21
30
|
|
22
31
|
def method_missing(meth, *args, &block)
|
23
|
-
rule = Schema::Rule.new([:val, [name, [:predicate, [meth, args]]]])
|
32
|
+
rule = Schema::Rule.new(name, [:val, [name, [:predicate, [meth, args]]]])
|
24
33
|
|
25
34
|
if block
|
26
35
|
val_rule = yield
|
@@ -28,7 +37,7 @@ module Dry
|
|
28
37
|
if val_rule.is_a?(Schema::Rule)
|
29
38
|
rule & val_rule
|
30
39
|
else
|
31
|
-
Schema::Rule.new([:and, [rule.to_ary, [:set, [name, rules.map(&:to_ary)]]]])
|
40
|
+
Schema::Rule.new(name, [:and, [rule.to_ary, [:set, [name, rules.map(&:to_ary)]]]])
|
32
41
|
end
|
33
42
|
else
|
34
43
|
rule
|
@@ -7,7 +7,7 @@ RSpec.describe Dry::Validation do
|
|
7
7
|
describe '#messages' do
|
8
8
|
it 'returns compiled error messages' do
|
9
9
|
expect(validation.(email: '').messages).to match_array([
|
10
|
-
[:email, [['Please provide your email', '']]
|
10
|
+
[:email, [['Please provide your email'], '']]
|
11
11
|
])
|
12
12
|
end
|
13
13
|
end
|
@@ -31,10 +31,10 @@ RSpec.describe Dry::Validation::ErrorCompiler do
|
|
31
31
|
|
32
32
|
it 'converts error ast into another format' do
|
33
33
|
expect(error_compiler.(ast)).to eql(
|
34
|
-
name: [["+name+ key is missing in the hash", nil]
|
35
|
-
age: [["age must be greater than 18", 18]
|
36
|
-
email: [["email must be filled", '']
|
37
|
-
address: [["Please provide your address", '']
|
34
|
+
name: [["+name+ key is missing in the hash"], nil],
|
35
|
+
age: [["age must be greater than 18"], 18],
|
36
|
+
email: [["email must be filled"], ''],
|
37
|
+
address: [["Please provide your address"], '']
|
38
38
|
)
|
39
39
|
end
|
40
40
|
end
|
@@ -42,228 +42,202 @@ RSpec.describe Dry::Validation::ErrorCompiler do
|
|
42
42
|
describe '#visit_predicate' do
|
43
43
|
describe ':empty?' do
|
44
44
|
it 'returns valid message' do
|
45
|
-
msg
|
45
|
+
msg = error_compiler.visit_predicate([:empty?, []], [], :tags)
|
46
46
|
|
47
|
-
expect(val).to eql([])
|
48
47
|
expect(msg).to eql('tags cannot be empty')
|
49
48
|
end
|
50
49
|
end
|
51
50
|
|
52
51
|
describe ':exclusion?' do
|
53
52
|
it 'returns valid message' do
|
54
|
-
msg
|
53
|
+
msg = error_compiler.visit_predicate([:exclusion?, [[1, 2, 3]]], 2, :num)
|
55
54
|
|
56
|
-
expect(val).to be(2)
|
57
55
|
expect(msg).to eql('num must not be one of: 1, 2, 3')
|
58
56
|
end
|
59
57
|
end
|
60
58
|
|
61
59
|
describe ':inclusion?' do
|
62
60
|
it 'returns valid message' do
|
63
|
-
msg
|
61
|
+
msg = error_compiler.visit_predicate([:inclusion?, [[1, 2, 3]]], 2, :num)
|
64
62
|
|
65
|
-
expect(val).to be(2)
|
66
63
|
expect(msg).to eql('num must be one of: 1, 2, 3')
|
67
64
|
end
|
68
65
|
end
|
69
66
|
|
70
67
|
describe ':gt?' do
|
71
68
|
it 'returns valid message' do
|
72
|
-
msg
|
69
|
+
msg = error_compiler.visit_predicate([:gt?, [3]], 2, :num)
|
73
70
|
|
74
|
-
expect(val).to be(2)
|
75
71
|
expect(msg).to eql('num must be greater than 3')
|
76
72
|
end
|
77
73
|
end
|
78
74
|
|
79
75
|
describe ':gteq?' do
|
80
76
|
it 'returns valid message' do
|
81
|
-
msg
|
77
|
+
msg = error_compiler.visit_predicate([:gteq?, [3]], 2, :num)
|
82
78
|
|
83
|
-
expect(val).to be(2)
|
84
79
|
expect(msg).to eql('num must be greater than or equal to 3')
|
85
80
|
end
|
86
81
|
end
|
87
82
|
|
88
83
|
describe ':lt?' do
|
89
84
|
it 'returns valid message' do
|
90
|
-
msg
|
85
|
+
msg = error_compiler.visit_predicate([:lt?, [3]], 2, :num)
|
91
86
|
|
92
|
-
expect(val).to be(2)
|
93
87
|
expect(msg).to eql('num must be less than 3 (2 was given)')
|
94
88
|
end
|
95
89
|
end
|
96
90
|
|
97
91
|
describe ':lteq?' do
|
98
92
|
it 'returns valid message' do
|
99
|
-
msg
|
93
|
+
msg = error_compiler.visit_predicate([:lteq?, [3]], 2, :num)
|
100
94
|
|
101
|
-
expect(val).to be(2)
|
102
95
|
expect(msg).to eql('num must be less than or equal to 3')
|
103
96
|
end
|
104
97
|
end
|
105
98
|
|
106
99
|
describe ':hash?' do
|
107
100
|
it 'returns valid message' do
|
108
|
-
msg
|
101
|
+
msg = error_compiler.visit_predicate([:hash?, []], '', :address)
|
109
102
|
|
110
|
-
expect(val).to eql('')
|
111
103
|
expect(msg).to eql('address must be a hash')
|
112
104
|
end
|
113
105
|
end
|
114
106
|
|
115
107
|
describe ':array?' do
|
116
108
|
it 'returns valid message' do
|
117
|
-
msg
|
109
|
+
msg = error_compiler.visit_predicate([:array?, []], '', :phone_numbers)
|
118
110
|
|
119
|
-
expect(val).to eql('')
|
120
111
|
expect(msg).to eql('phone_numbers must be an array')
|
121
112
|
end
|
122
113
|
end
|
123
114
|
|
124
115
|
describe ':int?' do
|
125
116
|
it 'returns valid message' do
|
126
|
-
msg
|
117
|
+
msg = error_compiler.visit_predicate([:int?, []], '2', :num)
|
127
118
|
|
128
|
-
expect(val).to eql('2')
|
129
119
|
expect(msg).to eql('num must be an integer')
|
130
120
|
end
|
131
121
|
end
|
132
122
|
|
133
123
|
describe ':float?' do
|
134
124
|
it 'returns valid message' do
|
135
|
-
msg
|
125
|
+
msg = error_compiler.visit_predicate([:float?, []], '2', :num)
|
136
126
|
|
137
|
-
expect(val).to eql('2')
|
138
127
|
expect(msg).to eql('num must be a float')
|
139
128
|
end
|
140
129
|
end
|
141
130
|
|
142
131
|
describe ':decimal?' do
|
143
132
|
it 'returns valid message' do
|
144
|
-
msg
|
133
|
+
msg = error_compiler.visit_predicate([:decimal?, []], '2', :num)
|
145
134
|
|
146
|
-
expect(val).to eql('2')
|
147
135
|
expect(msg).to eql('num must be a decimal')
|
148
136
|
end
|
149
137
|
end
|
150
138
|
|
151
139
|
describe ':date?' do
|
152
140
|
it 'returns valid message' do
|
153
|
-
msg
|
141
|
+
msg = error_compiler.visit_predicate([:date?, []], '2', :num)
|
154
142
|
|
155
|
-
expect(val).to eql('2')
|
156
143
|
expect(msg).to eql('num must be a date')
|
157
144
|
end
|
158
145
|
end
|
159
146
|
|
160
147
|
describe ':date_time?' do
|
161
148
|
it 'returns valid message' do
|
162
|
-
msg
|
149
|
+
msg = error_compiler.visit_predicate([:date_time?, []], '2', :num)
|
163
150
|
|
164
|
-
expect(val).to eql('2')
|
165
151
|
expect(msg).to eql('num must be a date time')
|
166
152
|
end
|
167
153
|
end
|
168
154
|
|
169
155
|
describe ':time?' do
|
170
156
|
it 'returns valid message' do
|
171
|
-
msg
|
157
|
+
msg = error_compiler.visit_predicate([:time?, []], '2', :num)
|
172
158
|
|
173
|
-
expect(val).to eql('2')
|
174
159
|
expect(msg).to eql('num must be a time')
|
175
160
|
end
|
176
161
|
end
|
177
162
|
|
178
163
|
describe ':max_size?' do
|
179
164
|
it 'returns valid message' do
|
180
|
-
msg
|
165
|
+
msg = error_compiler.visit_predicate([:max_size?, [3]], 'abcd', :num)
|
181
166
|
|
182
|
-
expect(val).to eql('abcd')
|
183
167
|
expect(msg).to eql('num size cannot be greater than 3')
|
184
168
|
end
|
185
169
|
end
|
186
170
|
|
187
171
|
describe ':min_size?' do
|
188
172
|
it 'returns valid message' do
|
189
|
-
msg
|
173
|
+
msg = error_compiler.visit_predicate([:min_size?, [3]], 'ab', :num)
|
190
174
|
|
191
|
-
expect(val).to eql('ab')
|
192
175
|
expect(msg).to eql('num size cannot be less than 3')
|
193
176
|
end
|
194
177
|
end
|
195
178
|
|
196
179
|
describe ':none?' do
|
197
180
|
it 'returns valid message' do
|
198
|
-
msg
|
181
|
+
msg = error_compiler.visit_predicate([:none?, []], nil, :num)
|
199
182
|
|
200
|
-
expect(val).to be(nil)
|
201
183
|
expect(msg).to eql('num cannot be defined')
|
202
184
|
end
|
203
185
|
end
|
204
186
|
|
205
187
|
describe ':size?' do
|
206
188
|
it 'returns valid message when val is array and arg is int' do
|
207
|
-
msg
|
189
|
+
msg = error_compiler.visit_predicate([:size?, [3]], [1], :numbers)
|
208
190
|
|
209
|
-
expect(val).to eql([1])
|
210
191
|
expect(msg).to eql('numbers size must be 3')
|
211
192
|
end
|
212
193
|
|
213
194
|
it 'returns valid message when val is array and arg is range' do
|
214
|
-
msg
|
195
|
+
msg = error_compiler.visit_predicate([:size?, [3..4]], [1], :numbers)
|
215
196
|
|
216
|
-
expect(val).to eql([1])
|
217
197
|
expect(msg).to eql('numbers size must be within 3 - 4')
|
218
198
|
end
|
219
199
|
|
220
200
|
it 'returns valid message when arg is int' do
|
221
|
-
msg
|
201
|
+
msg = error_compiler.visit_predicate([:size?, [3]], 'ab', :num)
|
222
202
|
|
223
|
-
expect(val).to eql('ab')
|
224
203
|
expect(msg).to eql('num length must be 3')
|
225
204
|
end
|
226
205
|
|
227
206
|
it 'returns valid message when arg is range' do
|
228
|
-
msg
|
207
|
+
msg = error_compiler.visit_predicate([:size?, [3..4]], 'ab', :num)
|
229
208
|
|
230
|
-
expect(val).to eql('ab')
|
231
209
|
expect(msg).to eql('num length must be within 3 - 4')
|
232
210
|
end
|
233
211
|
end
|
234
212
|
|
235
213
|
describe ':str?' do
|
236
214
|
it 'returns valid message' do
|
237
|
-
msg
|
215
|
+
msg = error_compiler.visit_predicate([:str?, []], 3, :num)
|
238
216
|
|
239
|
-
expect(val).to be(3)
|
240
217
|
expect(msg).to eql('num must be a string')
|
241
218
|
end
|
242
219
|
end
|
243
220
|
|
244
221
|
describe ':bool?' do
|
245
222
|
it 'returns valid message' do
|
246
|
-
msg
|
223
|
+
msg = error_compiler.visit_predicate([:bool?, []], 3, :num)
|
247
224
|
|
248
|
-
expect(val).to be(3)
|
249
225
|
expect(msg).to eql('num must be boolean')
|
250
226
|
end
|
251
227
|
end
|
252
228
|
|
253
229
|
describe ':format?' do
|
254
230
|
it 'returns valid message' do
|
255
|
-
msg
|
231
|
+
msg = error_compiler.visit_predicate([:format?, [/^F/]], 'Bar', :str)
|
256
232
|
|
257
|
-
expect(val).to eql('Bar')
|
258
233
|
expect(msg).to eql('str is in invalid format')
|
259
234
|
end
|
260
235
|
end
|
261
236
|
|
262
237
|
describe ':eql?' do
|
263
238
|
it 'returns valid message' do
|
264
|
-
msg
|
239
|
+
msg = error_compiler.visit_predicate([:eql?, ['Bar']], 'Foo', :str)
|
265
240
|
|
266
|
-
expect(val).to eql('Foo')
|
267
241
|
expect(msg).to eql('str must be equal to Bar')
|
268
242
|
end
|
269
243
|
end
|