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 '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) { odd? }
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) { odd? }
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(:odd?)
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(:odd?)
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(:odd?)
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(:odd?)
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(:odd?)
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(:odd?)
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
 
@@ -34,7 +34,6 @@ 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
37
  it 'is not successful' do
39
38
  expect(result).to be_failing ['length must be 3']
40
39
  end
@@ -184,7 +183,6 @@ RSpec.describe 'Predicates: Size' do
184
183
  let(:input) { { foo: '' } }
185
184
 
186
185
  it 'is not successful' do
187
- pending
188
186
  expect(result).to be_failing ['must be filled', 'length must be 3']
189
187
  end
190
188
  end
@@ -333,7 +331,6 @@ RSpec.describe 'Predicates: Size' do
333
331
  let(:input) { { foo: '' } }
334
332
 
335
333
  it 'is not successful' do
336
- pending
337
334
  expect(result).to be_failing ['must be filled', 'length must be 3']
338
335
  end
339
336
  end
@@ -181,7 +181,6 @@ RSpec.describe 'Predicates: Size' do
181
181
  let(:input) { { foo: '' } }
182
182
 
183
183
  it 'is not successful' do
184
- pending
185
184
  expect(result).to be_failing ['must be filled', 'length must be within 2 - 3']
186
185
  end
187
186
  end
@@ -329,7 +328,6 @@ RSpec.describe 'Predicates: Size' do
329
328
  let(:input) { { foo: '' } }
330
329
 
331
330
  it 'is not successful' do
332
- pending
333
331
  expect(result).to be_failing ['must be filled', 'length must be within 2 - 3']
334
332
  end
335
333
  end
@@ -388,4 +388,26 @@ RSpec.describe 'Predicates: Type' do
388
388
  end
389
389
  end
390
390
  end
391
+
392
+ context 'with a custom class' do
393
+ subject(:schema) do
394
+ Dry::Validation.Schema do
395
+ required(:foo).value(type?: CustomClass)
396
+ end
397
+ end
398
+
399
+ around do |example|
400
+ CustomClass = Class.new
401
+ example.run
402
+ Object.send(:remove_const, :CustomClass)
403
+ end
404
+
405
+ it 'it succeeds with valid input' do
406
+ expect(schema.(foo: CustomClass.new)).to be_success
407
+ end
408
+
409
+ it 'it fails with invalid input' do
410
+ expect(schema.(foo: 'oops')).to be_failing ["must be #{CustomClass}"]
411
+ end
412
+ end
391
413
  end
@@ -23,58 +23,31 @@ RSpec.describe Dry::Validation::Schema, 'defining schema using dry types' do
23
23
  end
24
24
 
25
25
  it 'passes when input is valid' do
26
- expect(schema.(email: 'jane@doe', age: 19, country: 'Australia')).to be_success
27
- expect(schema.(email: 'jane@doe', age: nil, country: 'Poland')).to be_success
26
+ expect(schema.call(email: 'jane@doe', age: 19, country: 'Australia')).to be_success
27
+ expect(schema.call(email: 'jane@doe', age: nil, country: 'Poland')).to be_success
28
28
  end
29
29
 
30
30
  it 'fails when input is not valid' do
31
- expect(schema.(email: '', age: 19, country: 'New Zealand')).to_not be_success
32
- expect(schema.(email: 'jane@doe', age: 17)).to_not be_success
33
- expect(schema.(email: 'jane@doe', age: '19')).to_not be_success
31
+ expect(schema.call(email: '', age: 19, country: 'New Zealand')).to_not be_success
32
+ expect(schema.call(email: 'jane@doe', age: 17)).to_not be_success
33
+ expect(schema.call(email: 'jane@doe', age: '19')).to_not be_success
34
34
  end
35
35
 
36
36
  it 'correctly responds to messages' do
37
- expect(schema.({}).messages).to eq(
38
- age: ["is missing", "must be greater than 18"],
39
- country: ["is missing", "must be one of: Australia, Poland"],
40
- email: ["is missing", "must be String"],
37
+ expect(schema.call({}).messages).to eq(
38
+ age: ['is missing', 'must be Integer', 'must be greater than 18'],
39
+ country: ['is missing', 'must be String', 'must be one of: Australia, Poland'],
40
+ email: ['is missing', 'must be String']
41
41
  )
42
42
  end
43
43
 
44
44
  it 'fails when sum-type rule did not pass' do
45
- result = schema.(email: 'jane@doe', age: 19, country: 'Australia', admin: 'foo')
45
+ result = schema.call(email: 'jane@doe', age: 19, country: 'Australia', admin: 'foo')
46
46
  expect(result.messages).to eql(
47
- admin: ['must be FalseClass', 'must be TrueClass']
47
+ admin: ['must be TrueClass or must be FalseClass']
48
48
  )
49
49
  end
50
50
 
51
- context "structs" do
52
- subject(:schema) do
53
- Dry::Validation.Schema do
54
- required(:person).filled(Person)
55
- end
56
- end
57
-
58
- class Name < Dry::Types::Value
59
- attribute :given_name, Dry::Types['strict.string']
60
- attribute :family_name, Dry::Types['strict.string']
61
- end
62
-
63
- class Person < Dry::Types::Value
64
- attribute :name, Name
65
- end
66
-
67
- it 'handles nested structs' do
68
- expect(schema.(person: { name: { given_name: 'Tim', family_name: 'Cooper' } })).to be_success
69
- end
70
-
71
- it 'fails when input is not valid' do
72
- expect(schema.(person: {name: {given_name: 'Tim'}}).messages).to eq(
73
- person: { name: { family_name: ["is missing"] } }
74
- )
75
- end
76
- end
77
-
78
51
  context 'custom coercions' do
79
52
  subject(:schema) do
80
53
  Dry::Validation.Schema do
@@ -85,7 +58,7 @@ RSpec.describe Dry::Validation::Schema, 'defining schema using dry types' do
85
58
  end
86
59
 
87
60
  it 'applies custom types to input prior validation' do
88
- result = schema.(email: ' jane@doe.org ')
61
+ result = schema.call(email: ' jane@doe.org ')
89
62
 
90
63
  expect(result).to be_success
91
64
  expect(result.to_h).to eql(email: 'jane@doe.org')
@@ -102,7 +75,7 @@ RSpec.describe Dry::Validation::Schema, 'defining schema using dry types' do
102
75
  end
103
76
 
104
77
  it 'applies custom types to input prior validation' do
105
- result = schema.(quantity: '2', percentage: '0.5', switch: '0')
78
+ result = schema.call(quantity: '2', percentage: '0.5', switch: '0')
106
79
 
107
80
  expect(result).to be_success
108
81
  expect(result.to_h).to eql(quantity: 2, percentage: BigDecimal('0.5'), switch: false)
@@ -117,7 +90,7 @@ RSpec.describe Dry::Validation::Schema, 'defining schema using dry types' do
117
90
  end
118
91
 
119
92
  it 'applies type constraint checks to each element' do
120
- result = schema.(countries: ['Poland', 'Australia'])
93
+ result = schema.call(countries: %w(Poland Australia))
121
94
 
122
95
  expect(result).to be_success
123
96
  expect(result.to_h).to eql(countries: %w(Poland Australia))
@@ -0,0 +1,83 @@
1
+ RSpec.describe Dry::Validation::Schema, 'arbitrary validation blocks' do
2
+ context 'with a single value' do
3
+ subject(:schema) do
4
+ Dry::Validation.Schema do
5
+ configure do
6
+ option :email_regex, /@/
7
+
8
+ def self.messages
9
+ super.merge(en: { errors: { email?: '%{value} looks like an invalid email' } })
10
+ end
11
+ end
12
+
13
+ required(:email).filled
14
+
15
+ validate(email?: :email) do |value|
16
+ email_regex.match(value)
17
+ end
18
+ end
19
+ end
20
+
21
+ it 'returns success for valid input' do
22
+ expect(schema.(email: 'jane@doe.org')).to be_success
23
+ end
24
+
25
+ it 'returns failure for invalid input' do
26
+ expect(schema.(email: 'jane')).to be_failure
27
+ end
28
+
29
+ it 'adds correct error message' do
30
+ expect(schema.(email: 'jane').messages).to eql(
31
+ email: ['jane looks like an invalid email']
32
+ )
33
+ end
34
+
35
+ it 'is not executed when deps are invalid' do
36
+ expect(schema.(email: nil)).to be_failure
37
+ end
38
+ end
39
+
40
+ context 'with more than one value' do
41
+ subject(:schema) do
42
+ Dry::Validation.Schema do
43
+ configure do
44
+ def self.messages
45
+ super.merge(en: { errors: { email_required: 'provide email' }})
46
+ end
47
+ end
48
+
49
+ required(:email).maybe(:str?)
50
+ required(:newsletter).value(:bool?)
51
+
52
+ validate(email_required: %i[newsletter email]) do |newsletter, email|
53
+ if newsletter == true
54
+ !email.nil?
55
+ else
56
+ true
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ it 'returns success for valid input' do
63
+ expect(schema.(newsletter: false, email: nil)).to be_success
64
+ expect(schema.(newsletter: true, email: 'jane@doe.org')).to be_success
65
+ end
66
+
67
+ it 'returns failure for invalid input' do
68
+ expect(schema.(newsletter: true, email: nil)).to be_failure
69
+ end
70
+
71
+ it 'adds correct error message' do
72
+ expect(schema.(newsletter: true, email: nil).errors).to eql(
73
+ email_required: ['provide email']
74
+ )
75
+ end
76
+
77
+ it 'is not executed when deps are invalid' do
78
+ expect(schema.(newsletter: 'oops', email: '').errors).to eql(
79
+ newsletter: ['must be boolean']
80
+ )
81
+ end
82
+ end
83
+ end
@@ -13,8 +13,8 @@ RSpec.describe 'Schema with xor rules' do
13
13
 
14
14
  required(:have_cake).filled
15
15
 
16
- rule(:be_reasonable) do
17
- value(:eat_cake).eql?('yes!') ^ value(:have_cake).eql?('yes!')
16
+ rule(be_reasonable: %i[eat_cake have_cake]) do |ec, hc|
17
+ ec.eql?('yes!') ^ hc.eql?('yes!')
18
18
  end
19
19
  end
20
20
  end
@@ -27,9 +27,9 @@ RSpec.describe 'Schema with xor rules' do
27
27
  end
28
28
 
29
29
  it 'fails when both options are selected' do
30
- messages = schema.(eat_cake: 'yes!', have_cake: 'yes!').messages[:be_reasonable]
31
-
32
- expect(messages).to eql(['you cannot eat cake and have cake!'])
30
+ expect(schema.(eat_cake: 'yes!', have_cake: 'yes!').messages).to eql(
31
+ be_reasonable: ['you cannot eat cake and have cake!']
32
+ )
33
33
  end
34
34
  end
35
35
  end
@@ -4,12 +4,14 @@ RSpec.describe 'Building schemas' do
4
4
  predicates = Module.new do
5
5
  include Dry::Logic::Predicates
6
6
 
7
- predicate(:zomg?) { true }
7
+ def zomg?(*)
8
+ true
9
+ end
8
10
  end
9
11
 
10
12
  schema = Dry::Validation.Schema(predicates: predicates, build: false)
11
13
 
12
- expect(schema.predicates.key?(:zomg?)).to be(true)
14
+ expect(schema.predicates[:key?]).to be_a(Method)
13
15
  end
14
16
  end
15
17
  end
@@ -162,4 +162,12 @@ RSpec.describe Dry::Validation::Schema, 'defining key-based schema' do
162
162
  end
163
163
  end
164
164
  end
165
+
166
+ context 'nested keys' do
167
+ it 'raises error when defining nested keys without `schema` block`' do
168
+ expect {
169
+ Dry::Validation.Schema { required(:foo).value { required(:bar).value(:str?) } }
170
+ }.to raise_error(ArgumentError, /required/)
171
+ end
172
+ end
165
173
  end
@@ -0,0 +1,11 @@
1
+ RSpec.shared_context :message_compiler do
2
+ subject(:compiler) { Dry::Validation::MessageCompiler.new(messages) }
3
+
4
+ let(:messages) do
5
+ Dry::Validation::Messages.default
6
+ end
7
+
8
+ let(:result) do
9
+ compiler.public_send(visitor, node)
10
+ end
11
+ end
@@ -1,13 +1,15 @@
1
- shared_context 'predicate helper' do
1
+ RSpec.shared_context 'predicate helper' do
2
2
  def p(name, *args)
3
- predicates[name].curry(*args).to_ast
3
+ Dry::Logic::Rule::Predicate.new(predicates[name], args: args).to_ast
4
4
  end
5
5
 
6
6
  let!(:predicates) do
7
7
  Module.new {
8
8
  include Dry::Logic::Predicates
9
9
 
10
- predicate(:email?) { |value| true }
10
+ def self.email?(value)
11
+ true
12
+ end
11
13
  }
12
14
  end
13
15
  end
data/spec/spec_helper.rb CHANGED
@@ -10,6 +10,7 @@ if RUBY_ENGINE == "rbx"
10
10
  end
11
11
 
12
12
  require 'dry-validation'
13
+ require 'dry/core/constants'
13
14
  require 'ostruct'
14
15
 
15
16
  SPEC_ROOT = Pathname(__dir__)
@@ -18,6 +19,7 @@ Dir[SPEC_ROOT.join('shared/**/*.rb')].each(&method(:require))
18
19
  Dir[SPEC_ROOT.join('support/**/*.rb')].each(&method(:require))
19
20
 
20
21
  include Dry::Validation
22
+ include Dry::Core::Constants
21
23
 
22
24
  module Types
23
25
  include Dry::Types.module
@@ -38,4 +40,17 @@ RSpec.configure do |config|
38
40
  end
39
41
 
40
42
  config.include PredicatesIntegration
43
+
44
+ config.before do
45
+ module Test
46
+ def self.remove_constants
47
+ constants.each { |const| remove_const(const) }
48
+ self
49
+ end
50
+ end
51
+ end
52
+
53
+ config.after do
54
+ Object.send(:remove_const, Test.remove_constants.name)
55
+ end
41
56
  end