dry-validation 0.8.0 → 0.9.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 (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
  )