dry-validation 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|