dry-validation 0.9.5 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -8
  3. data/CHANGELOG.md +34 -1
  4. data/Gemfile +2 -0
  5. data/Rakefile +12 -3
  6. data/config/errors.yml +1 -0
  7. data/dry-validation.gemspec +3 -2
  8. data/lib/dry/validation/executor.rb +3 -27
  9. data/lib/dry/validation/extensions/monads.rb +17 -0
  10. data/lib/dry/validation/extensions/struct.rb +32 -0
  11. data/lib/dry/validation/extensions.rb +7 -0
  12. data/lib/dry/validation/input_processor_compiler.rb +19 -3
  13. data/lib/dry/validation/message.rb +43 -30
  14. data/lib/dry/validation/message_compiler/visitor_opts.rb +39 -0
  15. data/lib/dry/validation/message_compiler.rb +98 -52
  16. data/lib/dry/validation/message_set.rb +59 -30
  17. data/lib/dry/validation/predicate_registry.rb +17 -6
  18. data/lib/dry/validation/result.rb +39 -17
  19. data/lib/dry/validation/schema/check.rb +5 -4
  20. data/lib/dry/validation/schema/class_interface.rb +6 -13
  21. data/lib/dry/validation/schema/dsl.rb +9 -3
  22. data/lib/dry/validation/schema/form.rb +12 -3
  23. data/lib/dry/validation/schema/json.rb +12 -3
  24. data/lib/dry/validation/schema/key.rb +6 -6
  25. data/lib/dry/validation/schema/rule.rb +14 -8
  26. data/lib/dry/validation/schema/value.rb +23 -21
  27. data/lib/dry/validation/schema.rb +9 -12
  28. data/lib/dry/validation/schema_compiler.rb +16 -2
  29. data/lib/dry/validation/version.rb +1 -1
  30. data/lib/dry/validation.rb +11 -23
  31. data/spec/extensions/monads/result_spec.rb +38 -0
  32. data/spec/extensions/struct/schema_spec.rb +32 -0
  33. data/spec/integration/custom_predicates_spec.rb +7 -6
  34. data/spec/integration/form/predicates/size/fixed_spec.rb +0 -2
  35. data/spec/integration/form/predicates/size/range_spec.rb +0 -2
  36. data/spec/integration/hints_spec.rb +2 -6
  37. data/spec/integration/json/defining_base_schema_spec.rb +41 -0
  38. data/spec/integration/{error_compiler_spec.rb → message_compiler_spec.rb} +79 -131
  39. data/spec/integration/result_spec.rb +26 -4
  40. data/spec/integration/schema/check_with_nested_el_spec.rb +1 -1
  41. data/spec/integration/schema/check_with_nth_el_spec.rb +1 -1
  42. data/spec/integration/schema/defining_base_schema_spec.rb +3 -0
  43. data/spec/integration/schema/dynamic_predicate_args_spec.rb +34 -9
  44. data/spec/integration/schema/form/defining_base_schema_spec.rb +41 -0
  45. data/spec/integration/schema/json_spec.rb +1 -0
  46. data/spec/integration/schema/macros/input_spec.rb +26 -0
  47. data/spec/integration/schema/macros/rule_spec.rb +2 -1
  48. data/spec/integration/schema/macros/value_spec.rb +1 -1
  49. data/spec/integration/schema/macros/when_spec.rb +1 -24
  50. data/spec/integration/schema/or_spec.rb +87 -0
  51. data/spec/integration/schema/predicates/custom_spec.rb +4 -4
  52. data/spec/integration/schema/predicates/even_spec.rb +10 -10
  53. data/spec/integration/schema/predicates/odd_spec.rb +10 -10
  54. data/spec/integration/schema/predicates/size/fixed_spec.rb +0 -3
  55. data/spec/integration/schema/predicates/size/range_spec.rb +0 -2
  56. data/spec/integration/schema/predicates/type_spec.rb +22 -0
  57. data/spec/integration/schema/using_types_spec.rb +14 -41
  58. data/spec/integration/schema/validate_spec.rb +83 -0
  59. data/spec/integration/schema/xor_spec.rb +5 -5
  60. data/spec/integration/schema_builders_spec.rb +4 -2
  61. data/spec/integration/schema_spec.rb +8 -0
  62. data/spec/shared/message_compiler.rb +11 -0
  63. data/spec/shared/predicate_helper.rb +5 -3
  64. data/spec/spec_helper.rb +15 -0
  65. data/spec/unit/input_processor_compiler/form_spec.rb +3 -3
  66. data/spec/unit/message_compiler/visit_failure_spec.rb +38 -0
  67. data/spec/unit/message_compiler/visit_spec.rb +16 -0
  68. data/spec/unit/message_compiler_spec.rb +7 -0
  69. data/spec/unit/predicate_registry_spec.rb +2 -2
  70. data/spec/unit/schema/key_spec.rb +19 -12
  71. data/spec/unit/schema/rule_spec.rb +14 -6
  72. data/spec/unit/schema/value_spec.rb +49 -52
  73. metadata +50 -20
  74. data/lib/dry/validation/constants.rb +0 -6
  75. data/lib/dry/validation/error.rb +0 -26
  76. data/lib/dry/validation/error_compiler.rb +0 -81
  77. data/lib/dry/validation/hint_compiler.rb +0 -104
  78. data/spec/unit/error_compiler_spec.rb +0 -7
  79. data/spec/unit/hint_compiler_spec.rb +0 -51
@@ -1,7 +1,7 @@
1
1
  RSpec.describe Dry::Validation::Result do
2
2
  subject(:result) { schema.(input) }
3
3
 
4
- let(:schema) { Dry::Validation.Schema { required(:name).filled(:str?) } }
4
+ let(:schema) { Dry::Validation.Schema { required(:name).filled(:str?, size?: 2..4) } }
5
5
 
6
6
  context 'with valid input' do
7
7
  let(:input) { { name: 'Jane' } }
@@ -42,17 +42,39 @@ RSpec.describe Dry::Validation::Result do
42
42
 
43
43
  describe '#messages' do
44
44
  it 'returns a hash with error messages' do
45
- expect(result.messages).to eql(name: ['must be filled'])
45
+ expect(result.messages).to eql(
46
+ name: ['must be filled', 'length must be within 2 - 4']
47
+ )
46
48
  end
47
49
 
48
50
  it 'with full: true returns full messages' do
49
- expect(result.messages(full: true)).to eql(name: ['name must be filled'])
51
+ expect(result.messages(full: true)).to eql(
52
+ name: ['name must be filled', 'name length must be within 2 - 4']
53
+ )
54
+ end
55
+ end
56
+
57
+ describe '#errors' do
58
+ let(:input) { { name: '' } }
59
+
60
+ it 'returns failure messages' do
61
+ expect(result.errors).to eql(name: ['must be filled'])
62
+ end
63
+ end
64
+
65
+ describe '#hints' do
66
+ let(:input) { { name: '' } }
67
+
68
+ it 'returns hint messages' do
69
+ expect(result.hints).to eql(name: ['length must be within 2 - 4'])
50
70
  end
51
71
  end
52
72
 
53
73
  describe '#message_set' do
54
74
  it 'returns message set' do
55
- expect(result.message_set.to_h).to eql(name: ['must be filled'])
75
+ expect(result.message_set.to_h).to eql(
76
+ name: ['must be filled', 'length must be within 2 - 4']
77
+ )
56
78
  end
57
79
  end
58
80
  end
@@ -7,7 +7,7 @@ RSpec.describe 'Check depending on a nested value from a hash' do
7
7
  end
8
8
  end
9
9
 
10
- rule(red: [[:tag, :color, :value]]) do |value|
10
+ rule(tag: [[:tag, :color, :value]]) do |value|
11
11
  value.eql?('red')
12
12
  end
13
13
  end
@@ -3,7 +3,7 @@ RSpec.describe 'Check depending on nth element in an array' do
3
3
  Dry::Validation.Schema do
4
4
  required(:tags).each(:str?)
5
5
 
6
- rule(red: [[:tags, 0]]) do |value|
6
+ rule(tags: [[:tags, 0]]) do |value|
7
7
  value.eql?('red')
8
8
  end
9
9
  end
@@ -1,3 +1,6 @@
1
+ require 'dry/validation/messages/i18n'
2
+ require 'i18n'
3
+
1
4
  RSpec.describe 'Defining base schema class' do
2
5
  subject(:schema) do
3
6
  Dry::Validation.Schema(BaseSchema) do
@@ -1,18 +1,43 @@
1
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)
2
+ context 'with base rules' do
3
+ subject(:schema) do
4
+ Dry::Validation.Schema do
5
+ configure do
6
+ def data
7
+ %w(a b c)
8
+ end
7
9
  end
10
+
11
+ required(:letter).filled(included_in?: data)
8
12
  end
13
+ end
9
14
 
10
- required(:letter).filled(included_in?: data)
15
+ it 'evaluates predicate arguments' do
16
+ expect(schema.(letter: 'a')).to be_success
17
+ expect(schema.(letter: 'f')).to be_failure
11
18
  end
12
19
  end
13
20
 
14
- it 'evaluates predicate arguments' do
15
- expect(schema.(letter: 'a')).to be_success
16
- expect(schema.(letter: 'f')).to be_failure
21
+ context 'with high-level rules' do
22
+ subject(:schema) do
23
+ Dry::Validation.Schema do
24
+ configure do
25
+ def data
26
+ %w(a b c)
27
+ end
28
+ end
29
+
30
+ required(:letter).filled(:str?)
31
+
32
+ rule(valid_letter: [:letter]) do |letter|
33
+ letter.included_in?(data)
34
+ end
35
+ end
36
+ end
37
+
38
+ it 'evaluates predicate arguments' do
39
+ expect(schema.(letter: 'a')).to be_success
40
+ expect(schema.(letter: 'f')).to be_failure
41
+ end
17
42
  end
18
43
  end
@@ -0,0 +1,41 @@
1
+ require 'dry/validation/messages/i18n'
2
+ require 'i18n'
3
+
4
+ RSpec.describe 'Defining base schema class' do
5
+ subject(:schema) do
6
+ Dry::Validation.Form(BaseSchema) do
7
+ required(:email).filled(:email?)
8
+ end
9
+ end
10
+
11
+ before do
12
+ class BaseSchema < Dry::Validation::Schema
13
+ configure do |config|
14
+ config.messages_file = SPEC_ROOT.join('fixtures/locales/en.yml')
15
+ config.messages = :i18n
16
+ end
17
+
18
+ def email?(value)
19
+ true
20
+ end
21
+
22
+ define! do
23
+ required(:name).filled
24
+ end
25
+ end
26
+ end
27
+
28
+ after do
29
+ Object.send(:remove_const, :BaseSchema)
30
+ end
31
+
32
+ it 'inherits predicates' do
33
+ expect(schema).to respond_to(:email?)
34
+ end
35
+
36
+ it 'inherits rules' do
37
+ expect(schema.('name' => '').messages).to eql(
38
+ name: ['must be filled'], email: ['is missing', 'must be an email']
39
+ )
40
+ end
41
+ end
@@ -154,6 +154,7 @@ RSpec.describe Dry::Validation::Schema::JSON, 'defining a schema' do
154
154
 
155
155
  it 'fails when nested schema fails' do
156
156
  result = schema.(delivery: true, address: { city: 'NYC', zipcode: 'foo' })
157
+
157
158
  expect(result.messages).to eql(
158
159
  address: { zipcode: ['must be an integer'] }
159
160
  )
@@ -110,4 +110,30 @@ RSpec.describe 'Macros #input' do
110
110
  )
111
111
  end
112
112
  end
113
+
114
+ context 'using a custom predicate' do
115
+ subject(:schema) do
116
+ Dry::Validation.Schema do
117
+ configure do
118
+ def valid_keys?(input)
119
+ input.size == 2 || input.size == 1
120
+ end
121
+ end
122
+
123
+ input :hash?, :valid_keys?
124
+
125
+ required(:foo).filled
126
+ optional(:bar).filled
127
+ end
128
+ end
129
+
130
+ it 'passes when input is valid' do
131
+ expect(schema.(foo: 'bar')).to be_successful
132
+ expect(schema.(foo: 'bar', bar: 'baz')).to be_successful
133
+ end
134
+
135
+ it 'fails when one of the root-rules fails' do
136
+ expect(schema.(foo: 'bar', bar: 'baz', oops: 'heh')).to be_failure
137
+ end
138
+ end
113
139
  end
@@ -67,7 +67,8 @@ RSpec.describe 'Macros / rule' do
67
67
 
68
68
  it 'fails when rules failed' do
69
69
  expect(schema.(x: 2).messages).to eql(
70
- x: ['must be greater than 3', 'must be greater than 5']
70
+ a: ['must be greater than 3'],
71
+ b: ['must be greater than 5']
71
72
  )
72
73
  end
73
74
  end
@@ -111,7 +111,7 @@ RSpec.describe 'Macros #value' do
111
111
 
112
112
  it 'uses the schema' do
113
113
  expect(schema.(data: { foo: '' }).messages).to eql(
114
- data: { foo: ['must be filled', 'size must be within 2 - 10'] }
114
+ data: { foo: ['must be filled', 'length must be within 2 - 10'] }
115
115
  )
116
116
  end
117
117
  end
@@ -40,30 +40,7 @@ RSpec.describe 'Macros #when' do
40
40
  end
41
41
  end
42
42
 
43
- describe 'with multiple result rules' do
44
- subject(:schema) do
45
- Dry::Validation.Schema do
46
- required(:email).maybe
47
- required(:password).maybe
48
-
49
- required(:login).maybe(:bool?).when(:true?) do
50
- value(:email).filled?
51
- value(:password).filled?
52
- end
53
- end
54
- end
55
-
56
- it 'generates check rule' do
57
- expect(schema.(login: false, email: nil, password: nil)).to be_success
58
-
59
- expect(schema.(login: true, email: nil, password: nil).messages).to eql(
60
- email: ['must be filled'],
61
- password: ['must be filled']
62
- )
63
- end
64
- end
65
-
66
- context "predicate with options" do
43
+ context 'predicate with options' do
67
44
  subject(:schema) do
68
45
  Dry::Validation.Schema do
69
46
  required(:bar).maybe
@@ -0,0 +1,87 @@
1
+ RSpec.describe Dry::Validation::Schema, 'OR messages' do
2
+ context 'with two predicates' do
3
+ subject(:schema) do
4
+ Dry::Validation.Schema do
5
+ required(:foo) { str? | int? }
6
+ end
7
+ end
8
+
9
+ it 'returns success for valid input' do
10
+ expect(schema.(foo: 'bar')).to be_success
11
+ expect(schema.(foo: 321)).to be_success
12
+ end
13
+
14
+ it 'provides OR error message for invalid input where all both sides failed' do
15
+ expect(schema.(foo: []).errors).to eql(foo: ['must be a string or must be an integer'])
16
+ end
17
+ end
18
+
19
+ context 'with a predicate and a conjunction of predicates' do
20
+ subject(:schema) do
21
+ Dry::Validation.Schema do
22
+ required(:foo) { str? | (int? & gt?(18)) }
23
+ end
24
+ end
25
+
26
+ it 'returns success for valid input' do
27
+ expect(schema.(foo: 'bar')).to be_success
28
+ expect(schema.(foo: 321)).to be_success
29
+ end
30
+
31
+ it 'provides OR message for invalid input where both sides failed' do
32
+ expect(schema.(foo: []).errors).to eql(foo: ['must be a string or must be an integer'])
33
+ end
34
+
35
+ it 'provides error messages for invalid input where right side failed' do
36
+ expect(schema.(foo: 17).errors).to eql(foo: ['must be a string or must be greater than 18'])
37
+ end
38
+ end
39
+
40
+ context 'with a predicate and an each operation' do
41
+ subject(:schema) do
42
+ Dry::Validation.Schema do
43
+ required(:foo) { str? | each(:int?) }
44
+ end
45
+ end
46
+
47
+ it 'returns success for valid input' do
48
+ expect(schema.(foo: 'bar')).to be_success
49
+ expect(schema.(foo: [1, 2, 3])).to be_success
50
+ end
51
+
52
+ it 'provides OR message for invalid input where both sides failed' do
53
+ expect(schema.(foo: {}).errors).to eql(foo: ['must be a string or must be an array'])
54
+ end
55
+
56
+ it 'provides error messages for invalid input where right side failed' do
57
+ expect(schema.(foo: %w(1 2 3)).errors).to eql(
58
+ foo: {
59
+ 0 => ['must be an integer'],
60
+ 1 => ['must be an integer'],
61
+ 2 => ['must be an integer']
62
+ }
63
+ )
64
+ end
65
+ end
66
+
67
+ context 'with a predicate and a schema' do
68
+ subject(:schema) do
69
+ Dry::Validation.Schema do
70
+ required(:foo) { str? | schema { required(:bar).filled } }
71
+ end
72
+ end
73
+
74
+ it 'returns success for valid input' do
75
+ expect(schema.(foo: 'bar')).to be_success
76
+ expect(schema.(foo: { bar: 'baz' })).to be_success
77
+ end
78
+
79
+ it 'provides OR message for invalid input where both sides failed' do
80
+ expect(schema.(foo: []).errors).to eql(foo: ['must be a string or must be a hash'])
81
+ end
82
+
83
+ it 'provides error messages for invalid input where right side rules failed' do
84
+ expect(schema.(foo: { bar: '' }).errors).to eql(foo: { bar: ['must be filled'] })
85
+ end
86
+ end
87
+ end
@@ -6,7 +6,7 @@ RSpec.describe 'Predicates: custom' do
6
6
  config.messages_file = 'spec/fixtures/locales/en.yml'
7
7
 
8
8
  def email?(current)
9
- current.match(/\@/)
9
+ !current.match(/\@/).nil?
10
10
  end
11
11
  end
12
12
 
@@ -39,8 +39,8 @@ RSpec.describe 'Predicates: custom' do
39
39
  predicates (Module.new do
40
40
  include Dry::Logic::Predicates
41
41
 
42
- predicate(:email?) do |current|
43
- current.match(/@/)
42
+ def self.email?(current)
43
+ !current.match(/\@/).nil?
44
44
  end
45
45
  end)
46
46
  end
@@ -97,7 +97,7 @@ RSpec.describe 'Predicates: custom' do
97
97
 
98
98
  it 'allows groups to define their own custom predicates' do
99
99
  expect(result).to_not be_success
100
- expect(result.messages.fetch(:details)).to eq(foo: ['must be odd'])
100
+ expect(result.messages[:details]).to eq(foo: ['must be odd'])
101
101
  end
102
102
  end
103
103
  end
@@ -1,7 +1,7 @@
1
1
  RSpec.describe 'Predicates: Even' do
2
2
  context 'with required' do
3
3
  subject(:schema) do
4
- Dry::Validation.Form do
4
+ Dry::Validation.Schema do
5
5
  required(:foo) { even? }
6
6
  end
7
7
  end
@@ -57,7 +57,7 @@ RSpec.describe 'Predicates: Even' do
57
57
 
58
58
  context 'with optional' do
59
59
  subject(:schema) do
60
- Dry::Validation.Form do
60
+ Dry::Validation.Schema do
61
61
  optional(:foo) { even? }
62
62
  end
63
63
  end
@@ -115,7 +115,7 @@ RSpec.describe 'Predicates: Even' do
115
115
  context 'with required' do
116
116
  context 'with value' do
117
117
  subject(:schema) do
118
- Dry::Validation.Form do
118
+ Dry::Validation.Schema do
119
119
  required(:foo).value(:even?)
120
120
  end
121
121
  end
@@ -171,7 +171,7 @@ RSpec.describe 'Predicates: Even' do
171
171
 
172
172
  context 'with filled' do
173
173
  subject(:schema) do
174
- Dry::Validation.Form do
174
+ Dry::Validation.Schema do
175
175
  required(:foo).filled(:even?)
176
176
  end
177
177
  end
@@ -227,7 +227,7 @@ RSpec.describe 'Predicates: Even' do
227
227
 
228
228
  context 'with maybe' do
229
229
  subject(:schema) do
230
- Dry::Validation.Form do
230
+ Dry::Validation.Schema do
231
231
  required(:foo).maybe(:even?)
232
232
  end
233
233
  end
@@ -260,7 +260,7 @@ RSpec.describe 'Predicates: Even' do
260
260
  let(:input) { { foo: '' } }
261
261
 
262
262
  it 'is successful' do
263
- expect(result).to be_successful
263
+ expect { result }.to raise_error(NoMethodError)
264
264
  end
265
265
  end
266
266
 
@@ -285,7 +285,7 @@ RSpec.describe 'Predicates: Even' do
285
285
  context 'with optional' do
286
286
  context 'with value' do
287
287
  subject(:schema) do
288
- Dry::Validation.Form do
288
+ Dry::Validation.Schema do
289
289
  optional(:foo).value(:even?)
290
290
  end
291
291
  end
@@ -341,7 +341,7 @@ RSpec.describe 'Predicates: Even' do
341
341
 
342
342
  context 'with filled' do
343
343
  subject(:schema) do
344
- Dry::Validation.Form do
344
+ Dry::Validation.Schema do
345
345
  optional(:foo).filled(:even?)
346
346
  end
347
347
  end
@@ -397,7 +397,7 @@ RSpec.describe 'Predicates: Even' do
397
397
 
398
398
  context 'with maybe' do
399
399
  subject(:schema) do
400
- Dry::Validation.Form do
400
+ Dry::Validation.Schema do
401
401
  optional(:foo).maybe(:even?)
402
402
  end
403
403
  end
@@ -430,7 +430,7 @@ RSpec.describe 'Predicates: Even' do
430
430
  let(:input) { { foo: '' } }
431
431
 
432
432
  it 'is successful' do
433
- expect(result).to be_successful
433
+ expect { result }.to raise_error(NoMethodError)
434
434
  end
435
435
  end
436
436