dry-validation 0.6.0 → 0.7.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -0
  3. data/.travis.yml +3 -2
  4. data/CHANGELOG.md +42 -0
  5. data/Gemfile +8 -1
  6. data/README.md +13 -89
  7. data/config/errors.yml +35 -29
  8. data/dry-validation.gemspec +2 -2
  9. data/examples/basic.rb +3 -7
  10. data/examples/each.rb +3 -8
  11. data/examples/form.rb +3 -6
  12. data/examples/nested.rb +7 -15
  13. data/lib/dry/validation.rb +33 -5
  14. data/lib/dry/validation/error.rb +10 -26
  15. data/lib/dry/validation/error_compiler.rb +69 -99
  16. data/lib/dry/validation/error_compiler/input.rb +148 -0
  17. data/lib/dry/validation/hint_compiler.rb +83 -33
  18. data/lib/dry/validation/input_processor_compiler.rb +98 -0
  19. data/lib/dry/validation/input_processor_compiler/form.rb +46 -0
  20. data/lib/dry/validation/input_processor_compiler/sanitizer.rb +46 -0
  21. data/lib/dry/validation/messages/abstract.rb +30 -10
  22. data/lib/dry/validation/messages/i18n.rb +2 -1
  23. data/lib/dry/validation/messages/namespaced.rb +1 -0
  24. data/lib/dry/validation/messages/yaml.rb +8 -5
  25. data/lib/dry/validation/result.rb +33 -25
  26. data/lib/dry/validation/schema.rb +168 -61
  27. data/lib/dry/validation/schema/attr.rb +5 -27
  28. data/lib/dry/validation/schema/check.rb +24 -0
  29. data/lib/dry/validation/schema/dsl.rb +97 -0
  30. data/lib/dry/validation/schema/form.rb +2 -26
  31. data/lib/dry/validation/schema/key.rb +32 -28
  32. data/lib/dry/validation/schema/rule.rb +88 -32
  33. data/lib/dry/validation/schema/value.rb +77 -27
  34. data/lib/dry/validation/schema_compiler.rb +38 -0
  35. data/lib/dry/validation/version.rb +1 -1
  36. data/spec/fixtures/locales/pl.yml +1 -1
  37. data/spec/integration/attr_spec.rb +122 -0
  38. data/spec/integration/custom_error_messages_spec.rb +9 -11
  39. data/spec/integration/custom_predicates_spec.rb +68 -18
  40. data/spec/integration/error_compiler_spec.rb +259 -65
  41. data/spec/integration/hints_spec.rb +28 -9
  42. data/spec/integration/injecting_rules_spec.rb +11 -12
  43. data/spec/integration/localized_error_messages_spec.rb +16 -16
  44. data/spec/integration/messages/i18n_spec.rb +9 -5
  45. data/spec/integration/optional_keys_spec.rb +9 -11
  46. data/spec/integration/schema/array_schema_spec.rb +23 -0
  47. data/spec/integration/schema/check_rules_spec.rb +39 -31
  48. data/spec/integration/schema/check_with_nth_el_spec.rb +25 -0
  49. data/spec/integration/schema/each_with_set_spec.rb +23 -24
  50. data/spec/integration/schema/form_spec.rb +122 -0
  51. data/spec/integration/schema/inheriting_schema_spec.rb +31 -0
  52. data/spec/integration/schema/input_processor_spec.rb +46 -0
  53. data/spec/integration/schema/macros/confirmation_spec.rb +33 -0
  54. data/spec/integration/schema/macros/maybe_spec.rb +32 -0
  55. data/spec/integration/schema/macros/required_spec.rb +59 -0
  56. data/spec/integration/schema/macros/when_spec.rb +65 -0
  57. data/spec/integration/schema/nested_values_spec.rb +41 -0
  58. data/spec/integration/schema/not_spec.rb +14 -14
  59. data/spec/integration/schema/option_with_default_spec.rb +30 -0
  60. data/spec/integration/schema/reusing_schema_spec.rb +33 -0
  61. data/spec/integration/schema/using_types_spec.rb +29 -0
  62. data/spec/integration/schema/xor_spec.rb +17 -14
  63. data/spec/integration/schema_spec.rb +75 -245
  64. data/spec/shared/rule_compiler.rb +8 -0
  65. data/spec/spec_helper.rb +13 -0
  66. data/spec/unit/hint_compiler_spec.rb +10 -10
  67. data/spec/unit/{input_type_compiler_spec.rb → input_processor_compiler/form_spec.rb} +88 -73
  68. data/spec/unit/schema/key_spec.rb +33 -0
  69. data/spec/unit/schema/rule_spec.rb +7 -6
  70. data/spec/unit/schema/value_spec.rb +187 -54
  71. metadata +53 -31
  72. data/.rubocop.yml +0 -16
  73. data/.rubocop_todo.yml +0 -7
  74. data/lib/dry/validation/input_type_compiler.rb +0 -83
  75. data/lib/dry/validation/schema/definition.rb +0 -74
  76. data/lib/dry/validation/schema/result.rb +0 -68
  77. data/rakelib/rubocop.rake +0 -18
  78. data/spec/integration/rule_groups_spec.rb +0 -94
  79. data/spec/integration/schema/attrs_spec.rb +0 -38
  80. data/spec/integration/schema/default_key_behavior_spec.rb +0 -23
  81. data/spec/integration/schema/grouped_rules_spec.rb +0 -57
  82. data/spec/integration/schema/nested_spec.rb +0 -31
  83. data/spec/integration/schema_form_spec.rb +0 -97
@@ -1,94 +0,0 @@
1
- RSpec.describe Dry::Validation::Schema do
2
- subject(:validation) { schema.new }
3
-
4
- before do
5
- def schema.messages
6
- Messages.default.merge(
7
- en: { errors: { password_confirmation: 'does not match' } }
8
- )
9
- end
10
- end
11
-
12
- describe 'defining schema with rule groups' do
13
- let(:schema) do
14
- Class.new(Dry::Validation::Schema) do
15
- confirmation(:password)
16
- end
17
- end
18
-
19
- describe '#call' do
20
- it 'returns empty errors when password matches confirmation' do
21
- expect(validation.(password: 'foo', password_confirmation: 'foo')).to be_empty
22
- end
23
-
24
- it 'returns error for a failed group rule' do
25
- expect(validation.(password: 'foo', password_confirmation: 'bar')).to match_array([
26
- [:error, [
27
- :input, [
28
- :password_confirmation,
29
- ["foo", "bar"],
30
- [[:group, [:password_confirmation, [:predicate, [:eql?, []]]]]]]]
31
- ]
32
- ])
33
- end
34
-
35
- it 'returns messages for a failed group rule' do
36
- expect(validation.(password: 'foo', password_confirmation: 'bar').messages).to eql(
37
- password_confirmation: [['does not match'], ['foo', 'bar']]
38
- )
39
- end
40
-
41
- it 'returns errors for the dependent predicates, not the group rule, when any of the dependent predicates fail' do
42
- expect(validation.(password: '', password_confirmation: '')).to match_array([
43
- [:error, [:input, [:password, "", [[:val, [:password, [:predicate, [:filled?, []]]]]]]]],
44
- [:error, [:input, [:password_confirmation, "", [[:val, [:password_confirmation, [:predicate, [:filled?, []]]]]]]]]
45
- ])
46
- end
47
- end
48
-
49
- describe 'confirmation' do
50
- shared_examples_for 'confirmation behavior' do
51
- it 'applies custom rules' do
52
- expect(validation.(password: 'abcd').messages).to include(
53
- password: [['password size cannot be less than 6'], 'abcd']
54
- )
55
- end
56
-
57
- it 'applies confirmation equality predicate' do
58
- expect(validation.(password: 'abcdef', password_confirmation: 'abcd').messages).to include(
59
- password_confirmation: [['does not match'], ['abcdef', 'abcd']]
60
- )
61
- end
62
-
63
- it 'skips default predicate' do
64
- expect(validation.(password: '', password_confirmation: '').messages).to include(
65
- password: [['password size cannot be less than 6'], ''],
66
- password_confirmation: [['password_confirmation must be filled'], '']
67
- )
68
- end
69
- end
70
-
71
- describe 'custom predicates' do
72
- let(:schema) do
73
- Class.new(Dry::Validation::Schema) do
74
- key(:password) { |value| value.min_size?(6) }
75
-
76
- confirmation(:password)
77
- end
78
- end
79
-
80
- it_behaves_like 'confirmation behavior'
81
- end
82
-
83
- describe 'custom predicates using shortcut options' do
84
- let(:schema) do
85
- Class.new(Dry::Validation::Schema) do
86
- confirmation(:password, min_size: 6)
87
- end
88
- end
89
-
90
- it_behaves_like 'confirmation behavior'
91
- end
92
- end
93
- end
94
- end
@@ -1,38 +0,0 @@
1
- require 'ostruct'
2
-
3
- RSpec.describe Schema, 'defining schema with attrs' do
4
- subject(:validation) { schema.new }
5
-
6
- let(:schema) do
7
- Class.new(Dry::Validation::Schema) do
8
- attr(:email) { |email| email.filled? }
9
-
10
- attr(:address) do |address|
11
- address.attr(:city, &:filled?)
12
- address.attr(:street, &:filled?)
13
- end
14
- end
15
- end
16
-
17
- describe '#call' do
18
- context 'when valid input' do
19
- let(:input) do
20
- struct_from_hash(email: "email@test.com", address: { city: 'NYC', street: 'Street 1/2' })
21
- end
22
-
23
- it 'should be valid' do
24
- expect(validation.(input)).to be_empty
25
- end
26
- end
27
-
28
- context 'when input does not have proper attributes' do
29
- let(:input) do
30
- struct_from_hash(name: "John", address: { country: 'US', street: 'Street 1/2' })
31
- end
32
-
33
- it 'should not be valid' do
34
- expect(validation.(input)).to_not be_empty
35
- end
36
- end
37
- end
38
- end
@@ -1,23 +0,0 @@
1
- RSpec.describe 'Schema::Form / Default key behavior' do
2
- subject(:validate) { schema.new }
3
-
4
- let(:schema) do
5
- Class.new(Dry::Validation::Schema::Form) do
6
- key(:name)
7
- key(:age, &:int?)
8
- optional(:address)
9
- end
10
- end
11
-
12
- it 'applies filled? predicate by default' do
13
- expect(validate.('name' => 'jane', 'age' => '21').params).to eql(
14
- name: 'jane', age: 21
15
- )
16
- end
17
-
18
- it 'applies filled? predicate by default to optional key' do
19
- expect(validate.('name' => 'jane', 'age' => '21', 'address' => 'Earth').params).to eql(
20
- name: 'jane', age: 21, address: 'Earth'
21
- )
22
- end
23
- end
@@ -1,57 +0,0 @@
1
- RSpec.describe Schema, 'using high-level grouped rules' do
2
- subject(:validate) { schema.new }
3
-
4
- let(:schema) do
5
- Class.new(Schema) do
6
- def self.messages
7
- Messages.default.merge(
8
- en: {
9
- errors: {
10
- email: {
11
- absence: 'email must not be selected',
12
- presence: 'email must be selected',
13
- format: 'this is not an email lol',
14
- inclusion: 'sorry, did not expect this lol'
15
- }
16
- }
17
- }
18
- )
19
- end
20
-
21
- key(:email) { |email| email.none? | email.filled? }
22
- key(:login) { |login| login.bool? }
23
-
24
- rule(email: :absence) do
25
- value(:login).false? > value(:email).none?
26
- end
27
-
28
- rule(email: :presence) do
29
- value(:login).true? > value(:email).filled?
30
- end
31
-
32
- rule(email: :format) do
33
- value(:email).filled? > value(:email).format?(/[a-z]@[a-z]/)
34
- end
35
-
36
- rule(email: :inclusion) do
37
- value(:email).filled? > value(:email).inclusion?(%w[jane@doe])
38
- end
39
- end
40
- end
41
-
42
- it 'passes when login is true and email is present' do
43
- expect(validate.(login: true, email: 'jane@doe').messages).to be_empty
44
- end
45
-
46
- it 'fails when login is false and email is not present' do
47
- expect(validate.(login: true, email: nil).messages).to_not be_empty
48
- end
49
-
50
- it 'provides merged error messages' do
51
- expect(validate.(login: true, email: 'not-an-email-lol').messages).to eql(
52
- email: [
53
- ["sorry, did not expect this lol", "this is not an email lol"], nil
54
- ]
55
- )
56
- end
57
- end
@@ -1,31 +0,0 @@
1
- RSpec.describe Schema, 'using nested schemas' do
2
- subject(:validate) { schema.new }
3
-
4
- let(:schema) do
5
- Class.new(Schema) do
6
- schema(:location) do |loc|
7
- loc.key(:lat, &:filled?)
8
- loc.key(:lng, &:filled?)
9
- end
10
- end
11
- end
12
-
13
- it 'passes when location has lat and lng filled' do
14
- expect(validate.(location: { lat: 1.23, lng: 4.56 })).to be_empty
15
- end
16
-
17
- it 'fails when location has missing lat' do
18
- expect(validate.(location: { lng: 4.56 })).to match_array([
19
- [
20
- :error, [
21
- :input, [
22
- :location, { lng: 4.56 },
23
- [
24
- [:input, [:lat, nil, [[:key, [:lat, [:predicate, [:key?, [:lat]]]]]]]]
25
- ]
26
- ]
27
- ]
28
- ]
29
- ])
30
- end
31
- end
@@ -1,97 +0,0 @@
1
- RSpec.describe Dry::Validation::Schema::Form do
2
- subject(:validation) { schema.new }
3
-
4
- describe 'defining schema' do
5
- let(:schema) do
6
- Class.new(Dry::Validation::Schema::Form) do
7
- key(:email) { |email| email.filled? }
8
-
9
- key(:age) { |age| age.none? | (age.int? & age.gt?(18)) }
10
-
11
- key(:address) do |address|
12
- address.hash? do
13
- address.key(:city, &:filled?)
14
- address.key(:street, &:filled?)
15
-
16
- address.key(:loc) do |loc|
17
- loc.key(:lat) { |lat| lat.filled? & lat.float? }
18
- loc.key(:lng) { |lng| lng.filled? & lng.float? }
19
- end
20
- end
21
- end
22
-
23
- optional(:phone_number) do |phone_number|
24
- phone_number.none? | (phone_number.int? & phone_number.gt?(0))
25
- end
26
- end
27
- end
28
-
29
- describe '#messages' do
30
- it 'returns compiled error messages' do
31
- result = validation.('email' => '', 'age' => '19')
32
-
33
- expect(result.messages).to match_array([
34
- [:email, [['email must be filled'], '']],
35
- [:address, [['address is missing'], nil]]
36
- ])
37
-
38
- expect(result.params).to eql(email: '', age: 19)
39
- end
40
- end
41
-
42
- describe '#call' do
43
- it 'passes when attributes are valid' do
44
- result = validation.(
45
- 'email' => 'jane@doe.org',
46
- 'age' => '19',
47
- 'address' => {
48
- 'city' => 'NYC',
49
- 'street' => 'Street 1/2',
50
- 'loc' => { 'lat' => '123.456', 'lng' => '456.123' }
51
- }
52
- )
53
-
54
- expect(result).to be_empty
55
-
56
- expect(result.params).to eql(
57
- email: 'jane@doe.org', age: 19,
58
- address: {
59
- city: 'NYC', street: 'Street 1/2',
60
- loc: { lat: 123.456, lng: 456.123 }
61
- }
62
- )
63
- end
64
-
65
- it 'validates presence of an email and min age value' do
66
- expect(validation.('email' => '', 'age' => '18')).to match_array([
67
- [:error, [:input, [:age, 18, [[:val, [:age, [:predicate, [:gt?, [18]]]]]]]]],
68
- [:error, [:input, [:email, "", [[:val, [:email, [:predicate, [:filled?, []]]]]]]]],
69
- [:error, [:input, [:address, nil, [[:key, [:address, [:predicate, [:key?, [:address]]]]]]]]]
70
- ])
71
- end
72
-
73
- it 'handles optionals' do
74
- result = validation.(
75
- 'email' => 'jane@doe.org',
76
- 'age' => '19',
77
- 'phone_number' => '12',
78
- 'address' => {
79
- 'city' => 'NYC',
80
- 'street' => 'Street 1/2',
81
- 'loc' => { 'lat' => '123.456', 'lng' => '456.123' }
82
- }
83
- )
84
-
85
- expect(result).to be_empty
86
-
87
- expect(result.params).to eql(
88
- email: 'jane@doe.org', age: 19, phone_number: 12,
89
- address: {
90
- city: 'NYC', street: 'Street 1/2',
91
- loc: { lat: 123.456, lng: 456.123 }
92
- }
93
- )
94
- end
95
- end
96
- end
97
- end