dry-validation 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +39 -1
  4. data/benchmarks/benchmark_schema_invalid_huge.rb +52 -0
  5. data/benchmarks/profile_schema_huge_invalid.rb +30 -0
  6. data/config/errors.yml +3 -2
  7. data/dry-validation.gemspec +2 -2
  8. data/lib/dry/validation.rb +20 -32
  9. data/lib/dry/validation/constants.rb +6 -0
  10. data/lib/dry/validation/error.rb +5 -2
  11. data/lib/dry/validation/error_compiler.rb +46 -116
  12. data/lib/dry/validation/executor.rb +105 -0
  13. data/lib/dry/validation/hint_compiler.rb +36 -68
  14. data/lib/dry/validation/message.rb +86 -0
  15. data/lib/dry/validation/message_compiler.rb +141 -0
  16. data/lib/dry/validation/message_set.rb +70 -0
  17. data/lib/dry/validation/messages/abstract.rb +1 -1
  18. data/lib/dry/validation/messages/i18n.rb +5 -0
  19. data/lib/dry/validation/predicate_registry.rb +8 -3
  20. data/lib/dry/validation/result.rb +6 -7
  21. data/lib/dry/validation/schema.rb +21 -227
  22. data/lib/dry/validation/schema/check.rb +1 -1
  23. data/lib/dry/validation/schema/class_interface.rb +193 -0
  24. data/lib/dry/validation/schema/deprecated.rb +1 -2
  25. data/lib/dry/validation/schema/key.rb +4 -0
  26. data/lib/dry/validation/schema/value.rb +12 -7
  27. data/lib/dry/validation/schema_compiler.rb +20 -1
  28. data/lib/dry/validation/type_specs.rb +70 -0
  29. data/lib/dry/validation/version.rb +1 -1
  30. data/spec/fixtures/locales/pl.yml +1 -1
  31. data/spec/integration/custom_predicates_spec.rb +37 -0
  32. data/spec/integration/error_compiler_spec.rb +39 -39
  33. data/spec/integration/form/predicates/key_spec.rb +10 -18
  34. data/spec/integration/form/predicates/size/fixed_spec.rb +8 -12
  35. data/spec/integration/form/predicates/size/range_spec.rb +7 -7
  36. data/spec/integration/hints_spec.rb +17 -0
  37. data/spec/integration/messages/i18n_spec.rb +2 -2
  38. data/spec/integration/schema/check_rules_spec.rb +2 -2
  39. data/spec/integration/schema/defining_base_schema_spec.rb +38 -0
  40. data/spec/integration/schema/dynamic_predicate_args_spec.rb +18 -0
  41. data/spec/integration/schema/macros/each_spec.rb +2 -2
  42. data/spec/integration/schema/macros/input_spec.rb +102 -10
  43. data/spec/integration/schema/macros/maybe_spec.rb +30 -0
  44. data/spec/integration/schema/nested_schemas_spec.rb +200 -0
  45. data/spec/integration/schema/nested_values_spec.rb +3 -1
  46. data/spec/integration/schema/option_with_default_spec.rb +54 -20
  47. data/spec/integration/schema/predicates/size/fixed_spec.rb +10 -10
  48. data/spec/integration/schema/predicates/size/range_spec.rb +8 -10
  49. data/spec/unit/error_compiler_spec.rb +1 -1
  50. data/spec/unit/hint_compiler_spec.rb +2 -2
  51. metadata +18 -7
  52. data/examples/rule_ast.rb +0 -25
  53. data/lib/dry/validation/error_compiler/input.rb +0 -135
@@ -0,0 +1,200 @@
1
+ RSpec.describe Schema, 'nested schemas' do
2
+ context 'with a 2-level deep schema' do
3
+ subject(:schema) do
4
+ Dry::Validation.Schema do
5
+ required(:meta).schema do
6
+ required(:info).schema do
7
+ required(:details).filled
8
+ required(:meta).filled
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ it 'passes when input is valid' do
15
+ expect(schema.(meta: { info: { details: 'Krakow', meta: 'foo' }})).to be_success
16
+ end
17
+
18
+ it 'fails when root key is missing' do
19
+ expect(schema.({}).messages).to eql(meta: ['is missing'])
20
+ end
21
+
22
+ it 'fails when 1-level key is missing' do
23
+ expect(schema.(meta: {}).messages).to eql(
24
+ meta: { info: ['is missing'] }
25
+ )
26
+ end
27
+
28
+ it 'fails when 2-level key is missing' do
29
+ expect(schema.(meta: { info: {} }).messages).to eql(
30
+ meta: { info: { details: ['is missing'], meta: ['is missing'] } }
31
+ )
32
+ end
33
+
34
+ it 'fails when 1-level key has invalid value' do
35
+ expect(schema.(meta: { info: nil, meta: 'foo' }).messages).to eql(
36
+ meta: { info: ['must be a hash'] }
37
+ )
38
+ end
39
+
40
+ it 'fails when 2-level key has invalid value' do
41
+ expect(schema.(meta: { info: { details: nil, meta: 'foo' } }).messages).to eql(
42
+ meta: { info: { details: ['must be filled'] } }
43
+ )
44
+ end
45
+ end
46
+
47
+ context 'when duplicated key names are used in 2 subsequent levels' do
48
+ subject(:schema) do
49
+ Dry::Validation.Schema do
50
+ required(:meta).schema do
51
+ required(:meta).filled
52
+ end
53
+ end
54
+ end
55
+
56
+ it 'passes when input is valid' do
57
+ expect(schema.(meta: { meta: 'data' })).to be_success
58
+ end
59
+
60
+ it 'fails when root key is missing' do
61
+ expect(schema.({}).messages).to eql(meta: ['is missing'])
62
+ end
63
+
64
+ it 'fails when 1-level key is missing' do
65
+ expect(schema.(meta: {}).messages).to eql(meta: { meta: ['is missing'] })
66
+ end
67
+
68
+ it 'fails when 1-level key value is invalid' do
69
+ expect(schema.(meta: { meta: '' }).messages).to eql(
70
+ meta: { meta: ['must be filled'] }
71
+ )
72
+ end
73
+ end
74
+
75
+ context 'when duplicated key names are used in 2 subsequent levels as schemas' do
76
+ subject(:schema) do
77
+ Dry::Validation.Schema do
78
+ required(:meta).schema do
79
+ required(:meta).schema do
80
+ required(:data).filled
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ it 'passes when input is valid' do
87
+ expect(schema.(meta: { meta: { data: 'this is fine' } })).to be_success
88
+ end
89
+
90
+ it 'fails when root key is missing' do
91
+ expect(schema.({}).messages).to eql(meta: ['is missing'])
92
+ end
93
+
94
+ it 'fails when 1-level key is missing' do
95
+ expect(schema.(meta: {}).messages).to eql(meta: { meta: ['is missing'] })
96
+ end
97
+
98
+ it 'fails when 1-level key value is invalid' do
99
+ expect(schema.(meta: { meta: '' }).messages).to eql(
100
+ meta: { meta: ['must be a hash'] }
101
+ )
102
+ end
103
+
104
+ it 'fails when 2-level key is missing' do
105
+ expect(schema.(meta: { meta: {} }).messages).to eql(
106
+ meta: { meta: { data: ['is missing'] } }
107
+ )
108
+ end
109
+
110
+ it 'fails when 2-level key value is invalid' do
111
+ expect(schema.(meta: { meta: { data: '' } }).messages).to eql(
112
+ meta: { meta: { data: ['must be filled'] } }
113
+ )
114
+ end
115
+ end
116
+
117
+ context 'with `each` + schema inside another schema' do
118
+ subject(:schema) do
119
+ Dry::Validation.Schema do
120
+ required(:meta).schema do
121
+ required(:data).each do
122
+ schema do
123
+ required(:info).schema do
124
+ required(:name).filled
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ it 'passes when data is valid' do
133
+ expect(schema.(meta: { data: [{ info: { name: 'oh hai' } }] })).to be_success
134
+ end
135
+
136
+ it 'fails when root key is missing' do
137
+ expect(schema.({}).messages).to eql(meta: ['is missing'])
138
+ end
139
+
140
+ it 'fails when root key value is invalid' do
141
+ expect(schema.(meta: '').messages).to eql(meta: ['must be a hash'])
142
+ end
143
+
144
+ it 'fails when 1-level key is missing' do
145
+ expect(schema.(meta: {}).messages).to eql(meta: { data: ['is missing'] })
146
+ end
147
+
148
+ it 'fails when 1-level key has invalid value' do
149
+ expect(schema.(meta: { data: '' }).messages).to eql(meta: { data: ['must be an array'] })
150
+ end
151
+
152
+ it 'fails when 1-level key has value with a missing key' do
153
+ expect(schema.(meta: { data: [{}] }).messages).to eql(
154
+ meta: { data: { 0 => { info: ['is missing'] } } }
155
+ )
156
+ end
157
+
158
+ it 'fails when 1-level key has value with an incorrect type' do
159
+ expect(schema.(meta: { data: [{ info: ''}] }).messages).to eql(
160
+ meta: { data: { 0 => { info: ['must be a hash'] } } }
161
+ )
162
+ end
163
+
164
+ it 'fails when 1-level key has value with a key with an invalid value' do
165
+ expect(schema.(meta: { data: [{ info: { name: '' } }] }).messages).to eql(
166
+ meta: { data: { 0 => { info: { name: ['must be filled'] } } } }
167
+ )
168
+ end
169
+ end
170
+
171
+ context 'with 2-level `each` + schema' do
172
+ subject(:schema) do
173
+ Dry::Validation.Schema do
174
+ required(:data).each do
175
+ schema do
176
+ required(:tags).each do
177
+ required(:name).filled
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ it 'passes when input is valid' do
185
+ expect(schema.(data: [{ tags: [{ name: 'red' }, { name: 'blue' }] }])).to be_success
186
+ end
187
+
188
+ it 'fails when 1-level element is not valid' do
189
+ expect(schema.(data: [{}]).messages).to eql(
190
+ data: { 0 => { tags: ['is missing'] } }
191
+ )
192
+ end
193
+
194
+ it 'fails when 2-level element is not valid' do
195
+ expect(schema.(data: [{ tags: [{ name: 'red' }, { name: '' }] }]).messages).to eql(
196
+ data: { 0 => { tags: { 1 => { name: ['must be filled'] } } } }
197
+ )
198
+ end
199
+ end
200
+ end
@@ -37,7 +37,9 @@ RSpec.describe Schema, 'using nested values' do
37
37
  end
38
38
 
39
39
  it 'fails when offers is true and newsletter is true' do
40
- expect(schema.(settings: { offers: true, newsletter: true }, email: 'jane@doe').messages).to eql(
40
+ input = { settings: { offers: true, newsletter: true }, email: 'jane@doe' }
41
+
42
+ expect(schema.(input).messages).to eql(
41
43
  settings: { newsletter: ['must be false'] }
42
44
  )
43
45
  end
@@ -1,30 +1,64 @@
1
- RSpec.describe Dry::Validation::Schema, 'defining an option with default value' do
2
- subject(:schema) do
3
- Dry::Validation.Schema do
4
- configure do
5
- option :db, -> { DB }
6
-
7
- def unique?(name, value)
8
- DB.none? { |item| item[name] == value }
9
- end
10
- end
1
+ RSpec.describe Dry::Validation::Schema, 'defining schema context with option API' do
2
+ shared_context 'valid schema with :db option' do
3
+ before do
4
+ DB = [{ email: 'jane@doe' }]
5
+ end
11
6
 
12
- required(:email) { filled? & unique?(:email) }
7
+ after do
8
+ Object.send(:remove_const, :DB)
13
9
  end
14
- end
15
10
 
16
- before do
17
- DB = [{ email: 'jane@doe' }]
11
+ it 'uses external dependency set by option with a default value' do
12
+ expect(schema.db).to be(DB)
13
+
14
+ expect(schema.(email: 'jade@doe', contact: { email: 'jade2@doe' })).to be_success
15
+ expect(schema.(email: 'jane@doe', contact: { email: 'jane2@doe' })).to be_failure
16
+ expect(schema.(email: 'jade@doe', contact: { email: 'jane@doe' })).to be_failure
17
+ end
18
18
  end
19
19
 
20
- after do
21
- Object.send(:remove_const, :DB)
20
+
21
+ context 'with a default value' do
22
+ subject(:schema) do
23
+ Dry::Validation.Schema do
24
+ configure do
25
+ option :db, -> { DB }
26
+
27
+ def unique?(name, value)
28
+ db.none? { |item| item[name] == value }
29
+ end
30
+ end
31
+
32
+ required(:email).filled(unique?: :email)
33
+
34
+ required(:contact).schema do
35
+ required(:email).filled(unique?: :email)
36
+ end
37
+ end
38
+ end
39
+
40
+ include_context 'valid schema with :db option'
22
41
  end
23
42
 
24
- it 'uses external dependency set by option with a default value' do
25
- expect(schema.db).to be(DB)
43
+ context 'without a default value' do
44
+ subject(:schema) do
45
+ Dry::Validation.Schema do
46
+ configure do
47
+ option :db
48
+
49
+ def unique?(name, value)
50
+ db.none? { |item| item[name] == value }
51
+ end
52
+ end
53
+
54
+ required(:email).filled(unique?: :email)
55
+
56
+ required(:contact).schema do
57
+ required(:email).filled(unique?: :email)
58
+ end
59
+ end.with(db: DB)
60
+ end
26
61
 
27
- expect(schema.(email: 'jade@doe')).to be_success
28
- expect(schema.(email: 'jane@doe')).to be_failure
62
+ include_context 'valid schema with :db option'
29
63
  end
30
64
  end
@@ -35,7 +35,7 @@ RSpec.describe 'Predicates: Size' do
35
35
  let(:input) { { foo: '' } }
36
36
 
37
37
  #see: https://github.com/dry-rb/dry-validation/issues/121
38
- xit 'is not successful' do
38
+ it 'is not successful' do
39
39
  expect(result).to be_failing ['length must be 3']
40
40
  end
41
41
  end
@@ -84,7 +84,7 @@ RSpec.describe 'Predicates: Size' do
84
84
  let(:input) { { foo: '' } }
85
85
 
86
86
  #see: https://github.com/dry-rb/dry-validation/issues/121
87
- xit 'is not successful' do
87
+ it 'is not successful' do
88
88
  expect(result).to be_failing ['length must be 3']
89
89
  end
90
90
  end
@@ -135,7 +135,7 @@ RSpec.describe 'Predicates: Size' do
135
135
  let(:input) { { foo: '' } }
136
136
 
137
137
  #see: https://github.com/dry-rb/dry-validation/issues/121
138
- xit 'is not successful' do
138
+ it 'is not successful' do
139
139
  expect(result).to be_failing ['length must be 3']
140
140
  end
141
141
  end
@@ -183,8 +183,8 @@ RSpec.describe 'Predicates: Size' do
183
183
  context 'with blank input' do
184
184
  let(:input) { { foo: '' } }
185
185
 
186
- #see: https://github.com/dry-rb/dry-validation/issues/121
187
- xit 'is not successful' do
186
+ it 'is not successful' do
187
+ pending
188
188
  expect(result).to be_failing ['must be filled', 'length must be 3']
189
189
  end
190
190
  end
@@ -233,7 +233,7 @@ RSpec.describe 'Predicates: Size' do
233
233
  let(:input) { { foo: '' } }
234
234
 
235
235
  #see: https://github.com/dry-rb/dry-validation/issues/121
236
- xit 'is not successful' do
236
+ it 'is not successful' do
237
237
  expect(result).to be_failing ['length must be 3']
238
238
  end
239
239
  end
@@ -284,7 +284,7 @@ RSpec.describe 'Predicates: Size' do
284
284
  let(:input) { { foo: '' } }
285
285
 
286
286
  #see: https://github.com/dry-rb/dry-validation/issues/121
287
- xit 'is not successful' do
287
+ it 'is not successful' do
288
288
  expect(result).to be_failing ['length must be 3']
289
289
  end
290
290
  end
@@ -332,8 +332,8 @@ RSpec.describe 'Predicates: Size' do
332
332
  context 'with blank input' do
333
333
  let(:input) { { foo: '' } }
334
334
 
335
- #see: https://github.com/dry-rb/dry-validation/issues/121
336
- xit 'is not successful' do
335
+ it 'is not successful' do
336
+ pending
337
337
  expect(result).to be_failing ['must be filled', 'length must be 3']
338
338
  end
339
339
  end
@@ -382,7 +382,7 @@ RSpec.describe 'Predicates: Size' do
382
382
  let(:input) { { foo: '' } }
383
383
 
384
384
  #see: https://github.com/dry-rb/dry-validation/issues/121
385
- xit 'is not successful' do
385
+ it 'is not successful' do
386
386
  expect(result).to be_failing ['length must be 3']
387
387
  end
388
388
  end
@@ -34,8 +34,7 @@ RSpec.describe 'Predicates: Size' do
34
34
  context 'with blank input' do
35
35
  let(:input) { { foo: '' } }
36
36
 
37
- #see: https://github.com/dry-rb/dry-validation/issues/121
38
- xit 'is not successful' do
37
+ it 'is not successful' do
39
38
  expect(result).to be_failing ['length must be within 2 - 3']
40
39
  end
41
40
  end
@@ -83,8 +82,7 @@ RSpec.describe 'Predicates: Size' do
83
82
  context 'with blank input' do
84
83
  let(:input) { { foo: '' } }
85
84
 
86
- #see: https://github.com/dry-rb/dry-validation/issues/121
87
- xit 'is not successful' do
85
+ it 'is not successful' do
88
86
  expect(result).to be_failing ['length must be within 2 - 3']
89
87
  end
90
88
  end
@@ -182,8 +180,8 @@ RSpec.describe 'Predicates: Size' do
182
180
  context 'with blank input' do
183
181
  let(:input) { { foo: '' } }
184
182
 
185
- #see: https://github.com/dry-rb/dry-validation/issues/121
186
- xit 'is not successful' do
183
+ it 'is not successful' do
184
+ pending
187
185
  expect(result).to be_failing ['must be filled', 'length must be within 2 - 3']
188
186
  end
189
187
  end
@@ -282,7 +280,7 @@ RSpec.describe 'Predicates: Size' do
282
280
  let(:input) { { foo: '' } }
283
281
 
284
282
  #see: https://github.com/dry-rb/dry-validation/issues/121
285
- xit 'is not successful' do
283
+ it 'is not successful' do
286
284
  expect(result).to be_failing ['length must be within 2 - 3']
287
285
  end
288
286
  end
@@ -330,8 +328,8 @@ RSpec.describe 'Predicates: Size' do
330
328
  context 'with blank input' do
331
329
  let(:input) { { foo: '' } }
332
330
 
333
- #see: https://github.com/dry-rb/dry-validation/issues/121
334
- xit 'is not successful' do
331
+ it 'is not successful' do
332
+ pending
335
333
  expect(result).to be_failing ['must be filled', 'length must be within 2 - 3']
336
334
  end
337
335
  end
@@ -380,7 +378,7 @@ RSpec.describe 'Predicates: Size' do
380
378
  let(:input) { { foo: '' } }
381
379
 
382
380
  #see: https://github.com/dry-rb/dry-validation/issues/121
383
- xit 'is not successful' do
381
+ it 'is not successful' do
384
382
  expect(result).to be_failing ['length must be within 2 - 3']
385
383
  end
386
384
  end
@@ -2,6 +2,6 @@ RSpec.describe ErrorCompiler, '#call' do
2
2
  subject(:error_compiler) { ErrorCompiler.new({}) }
3
3
 
4
4
  it 'returns an empty hash when there are no errors' do
5
- expect(error_compiler.([])).to be(ErrorCompiler::DEFAULT_RESULT)
5
+ expect(error_compiler.([])).to be_empty
6
6
  end
7
7
  end
@@ -28,7 +28,7 @@ RSpec.describe HintCompiler, '#call' do
28
28
  [:val, p(:key?, :height)],
29
29
  [
30
30
  :or, [
31
- [:attr, [:height, p(:none?)]],
31
+ [:key, [:height, p(:none?)]],
32
32
  [
33
33
  :and, [
34
34
  [:key, [:height, p(:int?)]],
@@ -43,7 +43,7 @@ RSpec.describe HintCompiler, '#call' do
43
43
  end
44
44
 
45
45
  it 'returns hint messages for given rules' do
46
- expect(compiler.call).to eql(
46
+ expect(compiler.call.to_h).to eql(
47
47
  age: ['must be greater than 18'],
48
48
  height: ['must be greater than 180'],
49
49
  )