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 '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