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.
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