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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/README.md +35 -499
  4. data/lib/dry/validation/error_compiler.rb +9 -4
  5. data/lib/dry/validation/hint_compiler.rb +69 -0
  6. data/lib/dry/validation/predicate.rb +0 -4
  7. data/lib/dry/validation/result.rb +8 -0
  8. data/lib/dry/validation/rule.rb +44 -0
  9. data/lib/dry/validation/rule/check.rb +15 -0
  10. data/lib/dry/validation/rule/composite.rb +20 -7
  11. data/lib/dry/validation/rule/result.rb +46 -0
  12. data/lib/dry/validation/rule_compiler.rb +14 -0
  13. data/lib/dry/validation/schema.rb +33 -3
  14. data/lib/dry/validation/schema/definition.rb +25 -4
  15. data/lib/dry/validation/schema/key.rb +8 -8
  16. data/lib/dry/validation/schema/result.rb +15 -2
  17. data/lib/dry/validation/schema/rule.rb +32 -5
  18. data/lib/dry/validation/schema/value.rb +15 -6
  19. data/lib/dry/validation/version.rb +1 -1
  20. data/spec/integration/custom_error_messages_spec.rb +1 -1
  21. data/spec/integration/error_compiler_spec.rb +30 -56
  22. data/spec/integration/hints_spec.rb +39 -0
  23. data/spec/integration/localized_error_messages_spec.rb +2 -2
  24. data/spec/integration/schema/check_rules_spec.rb +28 -0
  25. data/spec/integration/schema/each_with_set_spec.rb +71 -0
  26. data/spec/integration/schema/nested_spec.rb +31 -0
  27. data/spec/integration/schema/not_spec.rb +34 -0
  28. data/spec/integration/schema/xor_spec.rb +32 -0
  29. data/spec/integration/schema_form_spec.rb +2 -2
  30. data/spec/integration/schema_spec.rb +1 -1
  31. data/spec/shared/predicates.rb +2 -0
  32. data/spec/spec_helper.rb +2 -2
  33. data/spec/unit/hint_compiler_spec.rb +32 -0
  34. data/spec/unit/predicate_spec.rb +0 -10
  35. data/spec/unit/rule/check_spec.rb +29 -0
  36. data/spec/unit/rule_compiler_spec.rb +44 -7
  37. data/spec/unit/schema/rule_spec.rb +31 -0
  38. data/spec/unit/schema/value_spec.rb +84 -0
  39. 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
- predicate, rules = options.to_a.first
15
- identifier = { name => rules }
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
- groups << [:group, [identifier, [:predicate, predicate]]]
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
- def initialize(params, result, errors, error_compiler)
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 ||= error_compiler.with(options).(errors.map(&:to_ary))
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
- def initialize(node)
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 &(other)
17
- self.class.new([:and, [node, other.to_ary]])
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 |(other)
21
- self.class.new([:or, [node, other.to_ary]])
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
- rule = yield(self).to_ary
17
- Schema::Rule.new([:each, [name, rule]])
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
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module Validation
3
- VERSION = '0.3.1'.freeze
3
+ VERSION = '0.4.0'.freeze
4
4
  end
5
5
  end
@@ -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, val = error_compiler.visit_predicate([:empty?, []], [], :tags)
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, val = error_compiler.visit_predicate([:exclusion?, [[1, 2, 3]]], 2, :num)
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, val = error_compiler.visit_predicate([:inclusion?, [[1, 2, 3]]], 2, :num)
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, val = error_compiler.visit_predicate([:gt?, [3]], 2, :num)
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, val = error_compiler.visit_predicate([:gteq?, [3]], 2, :num)
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, val = error_compiler.visit_predicate([:lt?, [3]], 2, :num)
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, val = error_compiler.visit_predicate([:lteq?, [3]], 2, :num)
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, val = error_compiler.visit_predicate([:hash?, []], '', :address)
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, val = error_compiler.visit_predicate([:array?, []], '', :phone_numbers)
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, val = error_compiler.visit_predicate([:int?, []], '2', :num)
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, val = error_compiler.visit_predicate([:float?, []], '2', :num)
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, val = error_compiler.visit_predicate([:decimal?, []], '2', :num)
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, val = error_compiler.visit_predicate([:date?, []], '2', :num)
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, val = error_compiler.visit_predicate([:date_time?, []], '2', :num)
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, val = error_compiler.visit_predicate([:time?, []], '2', :num)
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, val = error_compiler.visit_predicate([:max_size?, [3]], 'abcd', :num)
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, val = error_compiler.visit_predicate([:min_size?, [3]], 'ab', :num)
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, val = error_compiler.visit_predicate([:none?, []], nil, :num)
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, val = error_compiler.visit_predicate([:size?, [3]], [1], :numbers)
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, val = error_compiler.visit_predicate([:size?, [3..4]], [1], :numbers)
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, val = error_compiler.visit_predicate([:size?, [3]], 'ab', :num)
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, val = error_compiler.visit_predicate([:size?, [3..4]], 'ab', :num)
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, val = error_compiler.visit_predicate([:str?, []], 3, :num)
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, val = error_compiler.visit_predicate([:bool?, []], 3, :num)
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, val = error_compiler.visit_predicate([:format?, [/^F/]], 'Bar', :str)
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, val = error_compiler.visit_predicate([:eql?, ['Bar']], 'Foo', :str)
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