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
@@ -1,28 +1,20 @@
1
- #see: https://github.com/dry-rb/dry-validation/issues/127
2
-
3
1
  RSpec.describe 'Predicates: Key' do
4
2
  context 'with required' do
5
- xit "should raise error" do
6
- expect { Dry::Validation.Form do
7
- required(:foo) { key? }
8
- end }.to raise_error InvalidSchemaError
3
+ it "raises error" do
4
+ expect { Dry::Validation.Form { required(:foo) { key? } } }.to raise_error InvalidSchemaError
9
5
  end
10
6
  end
11
7
 
12
8
  context 'with optional' do
13
- subject(:schema) do
14
- xit "should raise error" do
15
- expect { Dry::Validation.Form do
16
- optional(:foo) { key? }
17
- end }.to raise_error InvalidSchemaError
18
- end
9
+ it "raises error" do
10
+ expect { Dry::Validation.Form { optional(:foo) { key? } } }.to raise_error InvalidSchemaError
19
11
  end
20
12
  end
21
13
 
22
14
  context 'as macro' do
23
15
  context 'with required' do
24
16
  context 'with value' do
25
- xit "should raise error" do
17
+ it "raises error" do
26
18
  expect { Dry::Validation.Form do
27
19
  required(:foo).value(:key?)
28
20
  end }.to raise_error InvalidSchemaError
@@ -30,7 +22,7 @@ RSpec.describe 'Predicates: Key' do
30
22
  end
31
23
 
32
24
  context 'with filled' do
33
- xit "should raise error" do
25
+ it "raises error" do
34
26
  expect { Dry::Validation.Form do
35
27
  required(:foo).filled(:key?)
36
28
  end }.to raise_error InvalidSchemaError
@@ -38,7 +30,7 @@ RSpec.describe 'Predicates: Key' do
38
30
  end
39
31
 
40
32
  context 'with maybe' do
41
- xit "should raise error" do
33
+ it "raises error" do
42
34
  expect { Dry::Validation.Form do
43
35
  required(:foo).maybe(:key?)
44
36
  end }.to raise_error InvalidSchemaError
@@ -48,7 +40,7 @@ RSpec.describe 'Predicates: Key' do
48
40
 
49
41
  context 'with optional' do
50
42
  context 'with value' do
51
- xit "should raise error" do
43
+ it "raises error" do
52
44
  expect { Dry::Validation.Schema do
53
45
  optional(:foo).value(:key?)
54
46
  end }.to raise_error InvalidSchemaError
@@ -56,7 +48,7 @@ RSpec.describe 'Predicates: Key' do
56
48
  end
57
49
 
58
50
  context 'with filled' do
59
- xit "should raise error" do
51
+ it "raises error" do
60
52
  expect { Dry::Validation.Schema do
61
53
  optional(:foo).filled(:key?)
62
54
  end }.to raise_error InvalidSchemaError
@@ -64,7 +56,7 @@ RSpec.describe 'Predicates: Key' do
64
56
  end
65
57
 
66
58
  context 'with maybe' do
67
- xit "should raise error" do
59
+ it "raises error" do
68
60
  expect { Dry::Validation.Schema do
69
61
  optional(:foo).maybe(:key?)
70
62
  end }.to raise_error InvalidSchemaError
@@ -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 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 3']
89
87
  end
90
88
  end
@@ -134,8 +132,7 @@ RSpec.describe 'Predicates: Size' do
134
132
  context 'with blank input' do
135
133
  let(:input) { { 'foo' => '' } }
136
134
 
137
- #see: https://github.com/dry-rb/dry-validation/issues/121
138
- xit 'is not successful' do
135
+ it 'is not successful' do
139
136
  expect(result).to be_failing ['length must be 3']
140
137
  end
141
138
  end
@@ -183,8 +180,8 @@ RSpec.describe 'Predicates: Size' do
183
180
  context 'with blank input' do
184
181
  let(:input) { { 'foo' => '' } }
185
182
 
186
- #see: https://github.com/dry-rb/dry-validation/issues/121
187
- xit 'is not successful' do
183
+ it 'is not successful' do
184
+ pending
188
185
  expect(result).to be_failing ['must be filled', 'length must be 3']
189
186
  end
190
187
  end
@@ -282,8 +279,7 @@ RSpec.describe 'Predicates: Size' do
282
279
  context 'with blank input' do
283
280
  let(:input) { { 'foo' => '' } }
284
281
 
285
- #see: https://github.com/dry-rb/dry-validation/issues/121
286
- xit 'is not successful' do
282
+ it 'is not successful' do
287
283
  expect(result).to be_failing ['length must be 3']
288
284
  end
289
285
  end
@@ -331,8 +327,8 @@ RSpec.describe 'Predicates: Size' do
331
327
  context 'with blank input' do
332
328
  let(:input) { { 'foo' => '' } }
333
329
 
334
- #see: https://github.com/dry-rb/dry-validation/issues/121
335
- xit 'is not successful' do
330
+ it 'is not successful' do
331
+ pending
336
332
  expect(result).to be_failing ['must be filled', 'length must be 3']
337
333
  end
338
334
  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 within 2 - 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 within 2 - 3']
89
89
  end
90
90
  end
@@ -182,8 +182,8 @@ RSpec.describe 'Predicates: Size' do
182
182
  context 'with blank input' do
183
183
  let(:input) { { 'foo' => '' } }
184
184
 
185
- #see: https://github.com/dry-rb/dry-validation/issues/121
186
- xit 'is not successful' do
185
+ it 'is not successful' do
186
+ pending
187
187
  expect(result).to be_failing ['must be filled', 'length must be within 2 - 3']
188
188
  end
189
189
  end
@@ -282,7 +282,7 @@ RSpec.describe 'Predicates: Size' do
282
282
  let(:input) { { 'foo' => '' } }
283
283
 
284
284
  #see: https://github.com/dry-rb/dry-validation/issues/121
285
- xit 'is not successful' do
285
+ it 'is not successful' do
286
286
  expect(result).to be_failing ['length must be within 2 - 3']
287
287
  end
288
288
  end
@@ -330,8 +330,8 @@ RSpec.describe 'Predicates: Size' do
330
330
  context 'with blank input' do
331
331
  let(:input) { { 'foo' => '' } }
332
332
 
333
- #see: https://github.com/dry-rb/dry-validation/issues/121
334
- xit 'is not successful' do
333
+ it 'is not successful' do
334
+ pending
335
335
  expect(result).to be_failing ['must be filled', 'length must be within 2 - 3']
336
336
  end
337
337
  end
@@ -103,6 +103,23 @@ RSpec.describe 'Validation hints' do
103
103
  end
104
104
  end
105
105
 
106
+ context 'with an each rule' do
107
+ subject(:schema) do
108
+ Dry::Validation.Schema do
109
+ required(:nums).each(:int?, gt?: 0)
110
+ end
111
+ end
112
+
113
+ it 'provides hints for each element' do
114
+ expect(schema.(nums: [1, 'foo', 0]).messages).to eql(
115
+ nums: {
116
+ 1 => ['must be an integer', 'must be greater than 0'],
117
+ 2 => ['must be greater than 0']
118
+ }
119
+ )
120
+ end
121
+ end
122
+
106
123
  context 'when the message uses input value' do
107
124
  subject(:schema) do
108
125
  Dry::Validation.Schema do
@@ -42,7 +42,7 @@ RSpec.describe Messages::I18n do
42
42
  it 'returns a message for a specific rule and its arg type' do
43
43
  message = messages[:size?, rule: :pages, arg_type: Range]
44
44
 
45
- expect(message).to eql("size must be within %{left} - %{right}")
45
+ expect(message).to eql("size must be within %{size_left} - %{size_right}")
46
46
  end
47
47
  end
48
48
 
@@ -68,7 +68,7 @@ RSpec.describe Messages::I18n do
68
68
  it 'returns a message for a specific rule and its arg type' do
69
69
  message = messages[:size?, rule: :pages, arg_type: Range, locale: :pl]
70
70
 
71
- expect(message).to eql("wielkość musi być między %{left} a %{right}")
71
+ expect(message).to eql("wielkość musi być między %{size_left} a %{size_right}")
72
72
  end
73
73
  end
74
74
  end
@@ -28,8 +28,8 @@ RSpec.describe Schema, 'using high-level rules' do
28
28
  end
29
29
 
30
30
  it 'fails when red and blue are not filled ' do
31
- expect(schema.(red: nil, blue: nil).messages[:destiny]).to eql(
32
- ['you must select either red or blue']
31
+ expect(schema.(red: nil, blue: nil).messages).to eql(
32
+ destiny: ['you must select either red or blue']
33
33
  )
34
34
  end
35
35
  end
@@ -0,0 +1,38 @@
1
+ RSpec.describe 'Defining base schema class' do
2
+ subject(:schema) do
3
+ Dry::Validation.Schema(BaseSchema) do
4
+ required(:email).filled(:email?)
5
+ end
6
+ end
7
+
8
+ before do
9
+ class BaseSchema < Dry::Validation::Schema
10
+ configure do |config|
11
+ config.messages_file = SPEC_ROOT.join('fixtures/locales/en.yml')
12
+ config.messages = :i18n
13
+ end
14
+
15
+ def email?(value)
16
+ true
17
+ end
18
+
19
+ define! do
20
+ required(:name).filled
21
+ end
22
+ end
23
+ end
24
+
25
+ after do
26
+ Object.send(:remove_const, :BaseSchema)
27
+ end
28
+
29
+ it 'inherits predicates' do
30
+ expect(schema).to respond_to(:email?)
31
+ end
32
+
33
+ it 'inherits rules' do
34
+ expect(schema.(name: nil).messages).to eql(
35
+ name: ['must be filled'], email: ['is missing', 'must be an email']
36
+ )
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ RSpec.describe Dry::Validation::Schema, 'dynamic predicate args' do
2
+ subject(:schema) do
3
+ Dry::Validation.Schema do
4
+ configure do
5
+ def data
6
+ %w(a b c)
7
+ end
8
+ end
9
+
10
+ required(:letter).filled(included_in?: data)
11
+ end
12
+ end
13
+
14
+ it 'evaluates predicate arguments' do
15
+ expect(schema.(letter: 'a')).to be_success
16
+ expect(schema.(letter: 'f')).to be_failure
17
+ end
18
+ end
@@ -50,7 +50,7 @@ RSpec.describe 'Macros #each' do
50
50
  let(:input) { { foo: [[1,2], "foo"] } }
51
51
 
52
52
  it 'is not successful' do
53
- expect(result).to be_failing({0=>["size must be 3"]})
53
+ expect(result).to be_failing(0 => ["size must be 3"])
54
54
  end
55
55
  end
56
56
 
@@ -58,7 +58,7 @@ RSpec.describe 'Macros #each' do
58
58
  let(:input) { { foo: nil } }
59
59
 
60
60
  it 'is not successful' do
61
- expect(result).to be_failing ["must be an array", "size must be 3"]
61
+ expect(result).to be_failing ["must be an array"]
62
62
  end
63
63
  end
64
64
  end
@@ -1,21 +1,113 @@
1
1
  RSpec.describe 'Macros #input' do
2
- subject(:schema) do
3
- Dry::Validation.Schema do
4
- input :hash?
2
+ context 'with a flat schema' do
3
+ subject(:schema) do
4
+ Dry::Validation.Schema do
5
+ input :hash?
5
6
 
6
- required(:foo).filled
7
+ required(:foo).filled
8
+ end
9
+ end
10
+
11
+ it 'passes when input is valid' do
12
+ expect(schema.(foo: "bar")).to be_successful
13
+ end
14
+
15
+ it 'prepends a rule for the input' do
16
+ expect(schema.(nil).messages).to eql(["must be a hash"])
17
+ end
18
+
19
+ it 'applies other rules when input has expected type' do
20
+ expect(schema.(foo: '').messages).to eql(foo: ["must be filled"])
7
21
  end
8
22
  end
9
23
 
10
- it 'passes when input is valid' do
11
- expect(schema.(foo: "bar")).to be_successful
24
+ context 'with a nested schema' do
25
+ subject(:schema) do
26
+ Dry::Validation.Schema do
27
+ input(:hash?)
28
+
29
+ required(:foo).schema do
30
+ required(:bar).schema do
31
+ required(:baz).filled
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ it 'passes when input is valid' do
38
+ expect(schema.(foo: { bar: { baz: "present" }})).to be_successful
39
+ end
40
+
41
+ it 'fails when input has invalid type' do
42
+ expect(schema.(nil).messages).to eql(["must be a hash"])
43
+ end
44
+
45
+ it 'fails when 1-level key is missing' do
46
+ expect(schema.(foo: {}).messages).to eql(foo: { bar: ["is missing"] })
47
+ end
48
+
49
+ it 'fails when 2-level key has invalid value' do
50
+ expect(schema.(foo: { bar: { baz: '' }}).messages).to eql(
51
+ foo: { bar: { baz: ['must be filled'] } }
52
+ )
53
+ end
12
54
  end
13
55
 
14
- it 'prepends a rule for the input' do
15
- expect(schema.(nil).messages).to eql(["must be a hash"])
56
+ context 'when 2 nested schemas are under the same key' do
57
+ subject(:schema) do
58
+ Dry::Validation.Schema do
59
+ input :hash?
60
+
61
+ required(:meta).schema do
62
+ required(:meta).schema do
63
+ required(:data).filled
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ it 'passes when input is valid' do
70
+ expect(schema.(meta: { meta: { data: 'sutin' } })).to be_success
71
+ end
72
+
73
+ it 'fails when root key is missing' do
74
+ expect(schema.({}).messages).to eql(meta: ['is missing'])
75
+ end
76
+
77
+ it 'fails when 1-level key is missing' do
78
+ expect(schema.(meta: {}).messages).to eql(meta: { meta: ['is missing'] })
79
+ end
80
+
81
+ it 'fails when 1-level key value is invalid' do
82
+ expect(schema.(meta: { meta: '' }).messages).to eql(
83
+ meta: { meta: ['must be a hash'] }
84
+ )
85
+ end
86
+
87
+ it 'fails when 2-level key value is invalid' do
88
+ expect(schema.(meta: { meta: { data: '' } }).messages).to eql(
89
+ meta: { meta: { data: ['must be filled'] } }
90
+ )
91
+ end
16
92
  end
17
93
 
18
- it 'applies other rules when input has expected type' do
19
- expect(schema.(foo: '').messages).to eql(foo: ["must be filled"])
94
+ context 'using more than one predicate' do
95
+ subject(:schema) do
96
+ Dry::Validation.Schema do
97
+ input :hash?, size?: 2
98
+
99
+ required(:foo).filled
100
+ end
101
+ end
102
+
103
+ it 'passes when input is valid' do
104
+ expect(schema.(foo: "bar", bar: "baz")).to be_successful
105
+ end
106
+
107
+ it 'fails when one of the root-rules fails' do
108
+ expect(schema.(foo: "bar", bar: "baz", oops: "heh").messages).to eql(
109
+ ['size must be 2']
110
+ )
111
+ end
20
112
  end
21
113
  end
@@ -48,6 +48,36 @@ RSpec.describe 'Macros #maybe' do
48
48
  end
49
49
  end
50
50
 
51
+ describe 'with an optional key and a block with schema' do
52
+ subject(:schema) do
53
+ Dry::Validation.Schema do
54
+ optional(:employee).maybe do
55
+ schema do
56
+ required(:id).filled(:str?)
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ it 'passes when input is valid' do
63
+ expect(schema.(employee: { id: '1' })).to be_success
64
+ end
65
+
66
+ it 'passes when key is missing' do
67
+ expect(schema.({})).to be_success
68
+ end
69
+
70
+ it 'passes when value is nil' do
71
+ expect(schema.(employee: nil)).to be_success
72
+ end
73
+
74
+ it 'fails when value for nested schema is invalid' do
75
+ expect(schema.(employee: { id: 1 }).messages).to eql(
76
+ employee: { id: ['must be a string'] }
77
+ )
78
+ end
79
+ end
80
+
51
81
  describe 'with a predicate and a block' do
52
82
  subject(:schema) do
53
83
  Dry::Validation.Schema do