dry-validation 0.9.5 → 0.10.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 (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