dry-validation 0.1.0 → 0.2.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +23 -0
  4. data/README.md +203 -26
  5. data/config/errors.yml +16 -0
  6. data/dry-validation.gemspec +1 -0
  7. data/examples/each.rb +19 -0
  8. data/examples/form.rb +15 -0
  9. data/examples/nested.rb +13 -11
  10. data/lib/dry/validation.rb +5 -0
  11. data/lib/dry/validation/error_compiler.rb +2 -18
  12. data/lib/dry/validation/input_type_compiler.rb +78 -0
  13. data/lib/dry/validation/messages.rb +1 -7
  14. data/lib/dry/validation/predicates.rb +33 -1
  15. data/lib/dry/validation/result.rb +8 -0
  16. data/lib/dry/validation/rule.rb +11 -90
  17. data/lib/dry/validation/rule/composite.rb +50 -0
  18. data/lib/dry/validation/rule/each.rb +13 -0
  19. data/lib/dry/validation/rule/key.rb +17 -0
  20. data/lib/dry/validation/rule/set.rb +22 -0
  21. data/lib/dry/validation/rule/value.rb +13 -0
  22. data/lib/dry/validation/rule_compiler.rb +5 -0
  23. data/lib/dry/validation/schema.rb +6 -2
  24. data/lib/dry/validation/schema/definition.rb +4 -0
  25. data/lib/dry/validation/schema/form.rb +19 -0
  26. data/lib/dry/validation/schema/key.rb +16 -3
  27. data/lib/dry/validation/schema/result.rb +29 -0
  28. data/lib/dry/validation/schema/rule.rb +14 -16
  29. data/lib/dry/validation/schema/value.rb +14 -2
  30. data/lib/dry/validation/version.rb +1 -1
  31. data/spec/integration/custom_error_messages_spec.rb +1 -1
  32. data/spec/integration/optional_keys_spec.rb +30 -0
  33. data/spec/integration/schema_form_spec.rb +99 -0
  34. data/spec/integration/{validation_spec.rb → schema_spec.rb} +40 -13
  35. data/spec/shared/predicates.rb +1 -1
  36. data/spec/unit/error_compiler_spec.rb +64 -0
  37. data/spec/unit/input_type_compiler_spec.rb +205 -0
  38. data/spec/unit/predicates/bool_spec.rb +34 -0
  39. data/spec/unit/predicates/date_spec.rb +31 -0
  40. data/spec/unit/predicates/date_time_spec.rb +31 -0
  41. data/spec/unit/predicates/decimal_spec.rb +32 -0
  42. data/spec/unit/predicates/float_spec.rb +31 -0
  43. data/spec/unit/predicates/{nil_spec.rb → none_spec.rb} +2 -2
  44. data/spec/unit/predicates/time_spec.rb +31 -0
  45. data/spec/unit/rule/conjunction_spec.rb +28 -0
  46. data/spec/unit/rule/disjunction_spec.rb +36 -0
  47. data/spec/unit/rule/implication_spec.rb +14 -0
  48. data/spec/unit/rule/value_spec.rb +1 -1
  49. metadata +60 -6
@@ -0,0 +1,205 @@
1
+ require 'dry/validation/input_type_compiler'
2
+
3
+ RSpec.describe Dry::Validation::InputTypeCompiler, '#call' do
4
+ subject(:compiler) { Dry::Validation::InputTypeCompiler.new }
5
+
6
+ let(:rule_ast) do
7
+ [
8
+ [
9
+ :and, [
10
+ [:key, [:email, [:predicate, [:key?, [:email]]]]],
11
+ [
12
+ :and, [
13
+ [:val, [:email, [:predicate, [:str?, []]]]],
14
+ [:val, [:email, [:predicate, [:filled?, []]]]]
15
+ ]
16
+ ]
17
+ ]
18
+ ],
19
+ [
20
+ :and, [
21
+ [:key, [:age, [:predicate, [:key?, [:age]]]]],
22
+ [
23
+ :or, [
24
+ [:val, [:age, [:predicate, [:none?, []]]]],
25
+ [
26
+ :and, [
27
+ [:val, [:age, [:predicate, [:int?, []]]]],
28
+ [:val, [:age, [:predicate, [:filled?, []]]]]
29
+ ]
30
+ ]
31
+ ]
32
+ ]
33
+ ]
34
+ ],
35
+ [
36
+ :and, [
37
+ [:key, [:address, [:predicate, [:key?, [:address]]]]],
38
+ [:val, [:address, [:predicate, [:str?, []]]]]
39
+ ]
40
+ ]
41
+ ].map(&:to_ary)
42
+ end
43
+
44
+ let(:params) do
45
+ { 'email' => 'jane@doe.org', 'age' => '20', 'address' => 'City, Street 1/2' }
46
+ end
47
+
48
+ it 'builds an input dry-data type' do
49
+ input_type = compiler.(rule_ast)
50
+
51
+ result = input_type[params]
52
+
53
+ expect(result).to eql(email: 'jane@doe.org', age: 20, address: 'City, Street 1/2')
54
+ end
55
+
56
+ it 'supports int? => "form.int"' do
57
+ rule_ast = [
58
+ [
59
+ :and,
60
+ [
61
+ [:key, [:age, [:predicate, [:key?, [:age]]]]],
62
+ [:val, [:age, [:predicate, [:int?, []]]]],
63
+ ]
64
+ ]
65
+ ]
66
+
67
+ input_type = compiler.(rule_ast)
68
+
69
+ expect(input_type['age' => '21']).to eql(age: 21)
70
+ end
71
+
72
+ it 'supports none? => "form.int"' do
73
+ rule_ast = [
74
+ [
75
+ :and,
76
+ [
77
+ [:key, [:age, [:predicate, [:key?, [:age]]]]],
78
+ [
79
+ :or, [
80
+ [:val, [:age, [:predicate, [:none?, []]]]],
81
+ [:val, [:age, [:predicate, [:int?, []]]]],
82
+ ]
83
+ ]
84
+ ]
85
+ ]
86
+ ]
87
+
88
+ input_type = compiler.(rule_ast)
89
+
90
+ expect(input_type['age' => '']).to eql(age: nil)
91
+ expect(input_type['age' => '21']).to eql(age: 21)
92
+ end
93
+
94
+ it 'supports float? => "form.float"' do
95
+ rule_ast = [
96
+ [
97
+ :and,
98
+ [
99
+ [:key, [:lat, [:predicate, [:key?, [:lat]]]]],
100
+ [:val, [:lat, [:predicate, [:float?, []]]]],
101
+ ]
102
+ ]
103
+ ]
104
+
105
+ input_type = compiler.(rule_ast)
106
+
107
+ expect(input_type['lat' => '21.12']).to eql(lat: 21.12)
108
+ end
109
+
110
+ it 'supports decimal? => "form.decimal"' do
111
+ rule_ast = [
112
+ [
113
+ :and,
114
+ [
115
+ [:key, [:lat, [:predicate, [:key?, [:lat]]]]],
116
+ [:val, [:lat, [:predicate, [:decimal?, []]]]],
117
+ ]
118
+ ]
119
+ ]
120
+
121
+ input_type = compiler.(rule_ast)
122
+
123
+ expect(input_type['lat' => '21.12']).to eql(lat: 21.12.to_d)
124
+ end
125
+
126
+ it 'supports date? => "form.date"' do
127
+ rule_ast = [
128
+ [
129
+ :and,
130
+ [
131
+ [:key, [:bday, [:predicate, [:key?, [:bday]]]]],
132
+ [:val, [:bday, [:predicate, [:date?, []]]]],
133
+ ]
134
+ ]
135
+ ]
136
+
137
+ input_type = compiler.(rule_ast)
138
+
139
+ expect(input_type['bday' => '2012-01-23']).to eql(bday: Date.new(2012, 1, 23))
140
+ end
141
+
142
+ it 'supports date_time? => "form.date_time"' do
143
+ rule_ast = [
144
+ [
145
+ :and,
146
+ [
147
+ [:key, [:bday, [:predicate, [:key?, [:bday]]]]],
148
+ [:val, [:bday, [:predicate, [:date_time?, []]]]],
149
+ ]
150
+ ]
151
+ ]
152
+
153
+ input_type = compiler.(rule_ast)
154
+
155
+ expect(input_type['bday' => '2012-01-23 11:07']).to eql(bday: DateTime.new(2012, 1, 23, 11, 7))
156
+ end
157
+
158
+ it 'supports time? => "form.time"' do
159
+ rule_ast = [
160
+ [
161
+ :and,
162
+ [
163
+ [:key, [:bday, [:predicate, [:key?, [:bday]]]]],
164
+ [:val, [:bday, [:predicate, [:time?, []]]]],
165
+ ]
166
+ ]
167
+ ]
168
+
169
+ input_type = compiler.(rule_ast)
170
+
171
+ expect(input_type['bday' => '2012-01-23 11:07']).to eql(bday: Time.new(2012, 1, 23, 11, 7))
172
+ end
173
+
174
+ it 'supports bool? => "form.bool"' do
175
+ rule_ast = [
176
+ [
177
+ :and,
178
+ [
179
+ [:key, [:bday, [:predicate, [:key?, [:bday]]]]],
180
+ [:val, [:bday, [:predicate, [:time?, []]]]],
181
+ ]
182
+ ]
183
+ ]
184
+
185
+ input_type = compiler.(rule_ast)
186
+
187
+ expect(input_type['bday' => '2012-01-23 11:07']).to eql(bday: Time.new(2012, 1, 23, 11, 7))
188
+ end
189
+ it 'supports time? => "form.time"' do
190
+ rule_ast = [
191
+ [
192
+ :and,
193
+ [
194
+ [:key, [:admin, [:predicate, [:key?, [:admin]]]]],
195
+ [:val, [:admin, [:predicate, [:bool?, []]]]],
196
+ ]
197
+ ]
198
+ ]
199
+
200
+ input_type = compiler.(rule_ast)
201
+
202
+ expect(input_type['admin' => 'true']).to eql(admin: true)
203
+ expect(input_type['admin' => 'false']).to eql(admin: false)
204
+ end
205
+ end
@@ -0,0 +1,34 @@
1
+ require 'dry/validation/predicates'
2
+
3
+ RSpec.describe Dry::Validation::Predicates do
4
+ describe '#bool?' do
5
+ let(:predicate_name) { :bool? }
6
+
7
+ context 'when value is a date' do
8
+ let(:arguments_list) do
9
+ [[true], [false]]
10
+ end
11
+
12
+ it_behaves_like 'a passing predicate'
13
+ end
14
+
15
+ context 'with value is not an integer' do
16
+ let(:arguments_list) do
17
+ [
18
+ [''],
19
+ [[]],
20
+ [{}],
21
+ [nil],
22
+ [:symbol],
23
+ [String],
24
+ [1],
25
+ [0],
26
+ ['true'],
27
+ ['false']
28
+ ]
29
+ end
30
+
31
+ it_behaves_like 'a failing predicate'
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ require 'dry/validation/predicates'
2
+
3
+ RSpec.describe Dry::Validation::Predicates do
4
+ describe '#date?' do
5
+ let(:predicate_name) { :date? }
6
+
7
+ context 'when value is a date' do
8
+ let(:arguments_list) do
9
+ [[Date.today]]
10
+ end
11
+
12
+ it_behaves_like 'a passing predicate'
13
+ end
14
+
15
+ context 'with value is not an integer' do
16
+ let(:arguments_list) do
17
+ [
18
+ [''],
19
+ [[]],
20
+ [{}],
21
+ [nil],
22
+ [:symbol],
23
+ [String],
24
+ [1]
25
+ ]
26
+ end
27
+
28
+ it_behaves_like 'a failing predicate'
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ require 'dry/validation/predicates'
2
+
3
+ RSpec.describe Dry::Validation::Predicates do
4
+ describe '#date_time?' do
5
+ let(:predicate_name) { :date_time? }
6
+
7
+ context 'when value is a date' do
8
+ let(:arguments_list) do
9
+ [[DateTime.now]]
10
+ end
11
+
12
+ it_behaves_like 'a passing predicate'
13
+ end
14
+
15
+ context 'with value is not an integer' do
16
+ let(:arguments_list) do
17
+ [
18
+ [''],
19
+ [[]],
20
+ [{}],
21
+ [nil],
22
+ [:symbol],
23
+ [String],
24
+ [1]
25
+ ]
26
+ end
27
+
28
+ it_behaves_like 'a failing predicate'
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ require 'dry/validation/predicates'
2
+
3
+ RSpec.describe Dry::Validation::Predicates do
4
+ describe '#decimal?' do
5
+ let(:predicate_name) { :decimal? }
6
+
7
+ context 'when value is a date' do
8
+ let(:arguments_list) do
9
+ [[1.2.to_d]]
10
+ end
11
+
12
+ it_behaves_like 'a passing predicate'
13
+ end
14
+
15
+ context 'with value is not an integer' do
16
+ let(:arguments_list) do
17
+ [
18
+ [''],
19
+ [[]],
20
+ [{}],
21
+ [nil],
22
+ [:symbol],
23
+ [String],
24
+ [1],
25
+ [1.0]
26
+ ]
27
+ end
28
+
29
+ it_behaves_like 'a failing predicate'
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ require 'dry/validation/predicates'
2
+
3
+ RSpec.describe Dry::Validation::Predicates do
4
+ describe '#float?' do
5
+ let(:predicate_name) { :float? }
6
+
7
+ context 'when value is a float' do
8
+ let(:arguments_list) do
9
+ [[1.0]]
10
+ end
11
+
12
+ it_behaves_like 'a passing predicate'
13
+ end
14
+
15
+ context 'with value is not an integer' do
16
+ let(:arguments_list) do
17
+ [
18
+ [''],
19
+ [[]],
20
+ [{}],
21
+ [nil],
22
+ [:symbol],
23
+ [String],
24
+ [1]
25
+ ]
26
+ end
27
+
28
+ it_behaves_like 'a failing predicate'
29
+ end
30
+ end
31
+ end
@@ -1,8 +1,8 @@
1
1
  require 'dry/validation/predicates'
2
2
 
3
3
  RSpec.describe Dry::Validation::Predicates do
4
- describe '#nil?' do
5
- let(:predicate_name) { :nil? }
4
+ describe '#none?' do
5
+ let(:predicate_name) { :none? }
6
6
 
7
7
  context 'when value is nil' do
8
8
  let(:arguments_list) { [[nil]] }
@@ -0,0 +1,31 @@
1
+ require 'dry/validation/predicates'
2
+
3
+ RSpec.describe Dry::Validation::Predicates do
4
+ describe '#time?' do
5
+ let(:predicate_name) { :time? }
6
+
7
+ context 'when value is a date' do
8
+ let(:arguments_list) do
9
+ [[Time.now]]
10
+ end
11
+
12
+ it_behaves_like 'a passing predicate'
13
+ end
14
+
15
+ context 'with value is not an integer' do
16
+ let(:arguments_list) do
17
+ [
18
+ [''],
19
+ [[]],
20
+ [{}],
21
+ [nil],
22
+ [:symbol],
23
+ [String],
24
+ [1]
25
+ ]
26
+ end
27
+
28
+ it_behaves_like 'a failing predicate'
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ RSpec.describe Rule::Composite::Conjunction do
2
+ subject(:rule) { Rule::Composite::Conjunction.new(left, right) }
3
+
4
+ let(:left) { Rule::Value.new(:age, Predicates[:int?]) }
5
+ let(:right) { Rule::Value.new(:age, Predicates[:gt?].curry(18)) }
6
+
7
+ describe '#call' do
8
+ it 'calls left and right' do
9
+ expect(rule.(18)).to be_failure
10
+ end
11
+ end
12
+
13
+ describe '#and' do
14
+ let(:other) { Rule::Value.new(:age, Predicates[:lt?].curry(30)) }
15
+
16
+ it 'creates conjunction with the other' do
17
+ expect(rule.and(other).(31)).to be_failure
18
+ end
19
+ end
20
+
21
+ describe '#or' do
22
+ let(:other) { Rule::Value.new(:age, Predicates[:lt?].curry(14)) }
23
+
24
+ it 'creates disjunction with the other' do
25
+ expect(rule.or(other).(13)).to be_success
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,36 @@
1
+ RSpec.describe Rule::Composite::Disjunction do
2
+ subject(:rule) { Rule::Composite::Disjunction.new(left, right) }
3
+
4
+ let(:left) { Rule::Value.new(:age, Predicates[:none?]) }
5
+ let(:right) { Rule::Value.new(:age, Predicates[:gt?].curry(18)) }
6
+
7
+ let(:other) do
8
+ Rule::Value.new(:age, Predicates[:int?]) & Rule::Value.new(:age, Predicates[:lt?].curry(14))
9
+ end
10
+
11
+ describe '#call' do
12
+ it 'calls left and right' do
13
+ expect(rule.(nil)).to be_success
14
+ expect(rule.(19)).to be_success
15
+ expect(rule.(18)).to be_failure
16
+ end
17
+ end
18
+
19
+ describe '#and' do
20
+ it 'creates conjunction with the other' do
21
+ expect(rule.and(other).(nil)).to be_failure
22
+ expect(rule.and(other).(19)).to be_failure
23
+ expect(rule.and(other).(13)).to be_failure
24
+ expect(rule.and(other).(14)).to be_failure
25
+ end
26
+ end
27
+
28
+ describe '#or' do
29
+ it 'creates disjunction with the other' do
30
+ expect(rule.or(other).(nil)).to be_success
31
+ expect(rule.or(other).(19)).to be_success
32
+ expect(rule.or(other).(13)).to be_success
33
+ expect(rule.or(other).(14)).to be_failure
34
+ end
35
+ end
36
+ end