dry-validation 0.7.4 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +9 -6
  4. data/CHANGELOG.md +58 -0
  5. data/Gemfile +3 -3
  6. data/benchmarks/benchmark_form_invalid.rb +64 -0
  7. data/benchmarks/benchmark_form_valid.rb +64 -0
  8. data/benchmarks/profile_schema_call_invalid.rb +20 -0
  9. data/benchmarks/profile_schema_call_valid.rb +20 -0
  10. data/benchmarks/profile_schema_definition.rb +14 -0
  11. data/benchmarks/profile_schema_messages_invalid.rb +20 -0
  12. data/benchmarks/suite.rb +5 -0
  13. data/config/errors.yml +12 -5
  14. data/dry-validation.gemspec +2 -2
  15. data/examples/basic.rb +2 -2
  16. data/examples/form.rb +2 -2
  17. data/examples/json.rb +2 -2
  18. data/examples/nested.rb +6 -6
  19. data/lib/dry/validation.rb +22 -3
  20. data/lib/dry/validation/deprecations.rb +23 -0
  21. data/lib/dry/validation/error_compiler.rb +31 -11
  22. data/lib/dry/validation/error_compiler/input.rb +44 -57
  23. data/lib/dry/validation/hint_compiler.rb +15 -7
  24. data/lib/dry/validation/input_processor_compiler.rb +13 -6
  25. data/lib/dry/validation/input_processor_compiler/form.rb +2 -0
  26. data/lib/dry/validation/input_processor_compiler/sanitizer.rb +1 -1
  27. data/lib/dry/validation/messages/abstract.rb +9 -1
  28. data/lib/dry/validation/predicate_registry.rb +101 -0
  29. data/lib/dry/validation/result.rb +8 -1
  30. data/lib/dry/validation/schema.rb +110 -44
  31. data/lib/dry/validation/schema/check.rb +4 -2
  32. data/lib/dry/validation/schema/deprecated.rb +31 -0
  33. data/lib/dry/validation/schema/dsl.rb +18 -11
  34. data/lib/dry/validation/schema/form.rb +1 -0
  35. data/lib/dry/validation/schema/json.rb +1 -0
  36. data/lib/dry/validation/schema/key.rb +23 -10
  37. data/lib/dry/validation/schema/rule.rb +81 -20
  38. data/lib/dry/validation/schema/value.rb +110 -25
  39. data/lib/dry/validation/version.rb +1 -1
  40. data/spec/fixtures/locales/en.yml +1 -0
  41. data/spec/fixtures/locales/pl.yml +1 -1
  42. data/spec/integration/custom_error_messages_spec.rb +2 -2
  43. data/spec/integration/custom_predicates_spec.rb +98 -15
  44. data/spec/integration/error_compiler_spec.rb +111 -96
  45. data/spec/integration/form/predicates/array_spec.rb +243 -0
  46. data/spec/integration/form/predicates/empty_spec.rb +263 -0
  47. data/spec/integration/form/predicates/eql_spec.rb +327 -0
  48. data/spec/integration/form/predicates/even_spec.rb +455 -0
  49. data/spec/integration/form/predicates/excluded_from_spec.rb +455 -0
  50. data/spec/integration/form/predicates/excludes_spec.rb +391 -0
  51. data/spec/integration/form/predicates/false_spec.rb +455 -0
  52. data/spec/integration/form/predicates/filled_spec.rb +467 -0
  53. data/spec/integration/form/predicates/format_spec.rb +454 -0
  54. data/spec/integration/form/predicates/gt_spec.rb +519 -0
  55. data/spec/integration/form/predicates/gteq_spec.rb +519 -0
  56. data/spec/integration/form/predicates/included_in_spec.rb +455 -0
  57. data/spec/integration/form/predicates/includes_spec.rb +391 -0
  58. data/spec/integration/form/predicates/key_spec.rb +75 -0
  59. data/spec/integration/form/predicates/lt_spec.rb +519 -0
  60. data/spec/integration/form/predicates/lteq_spec.rb +519 -0
  61. data/spec/integration/form/predicates/max_size_spec.rb +391 -0
  62. data/spec/integration/form/predicates/min_size_spec.rb +391 -0
  63. data/spec/integration/form/predicates/none_spec.rb +265 -0
  64. data/spec/integration/form/predicates/not_eql_spec.rb +327 -0
  65. data/spec/integration/form/predicates/odd_spec.rb +455 -0
  66. data/spec/integration/form/predicates/size/fixed_spec.rb +399 -0
  67. data/spec/integration/form/predicates/size/range_spec.rb +398 -0
  68. data/spec/integration/form/predicates/true_spec.rb +455 -0
  69. data/spec/integration/form/predicates/type_spec.rb +391 -0
  70. data/spec/integration/hints_spec.rb +90 -4
  71. data/spec/integration/injecting_rules_spec.rb +2 -2
  72. data/spec/integration/localized_error_messages_spec.rb +2 -2
  73. data/spec/integration/messages/i18n_spec.rb +3 -3
  74. data/spec/integration/optional_keys_spec.rb +3 -3
  75. data/spec/integration/schema/array_schema_spec.rb +49 -13
  76. data/spec/integration/schema/check_rules_spec.rb +6 -6
  77. data/spec/integration/schema/check_with_nested_el_spec.rb +3 -3
  78. data/spec/integration/schema/check_with_nth_el_spec.rb +1 -1
  79. data/spec/integration/schema/each_with_set_spec.rb +3 -3
  80. data/spec/integration/schema/extending_dsl_spec.rb +27 -0
  81. data/spec/integration/schema/form/explicit_types_spec.rb +182 -0
  82. data/spec/integration/schema/form_spec.rb +90 -18
  83. data/spec/integration/schema/hash_schema_spec.rb +47 -0
  84. data/spec/integration/schema/inheriting_schema_spec.rb +4 -4
  85. data/spec/integration/schema/input_processor_spec.rb +8 -8
  86. data/spec/integration/schema/json/explicit_types_spec.rb +157 -0
  87. data/spec/integration/schema/json_spec.rb +18 -18
  88. data/spec/integration/schema/macros/confirmation_spec.rb +1 -1
  89. data/spec/integration/schema/macros/each_spec.rb +177 -43
  90. data/spec/integration/schema/macros/{required_spec.rb → filled_spec.rb} +34 -6
  91. data/spec/integration/schema/macros/input_spec.rb +21 -0
  92. data/spec/integration/schema/macros/maybe_spec.rb +39 -2
  93. data/spec/integration/schema/macros/value_spec.rb +105 -0
  94. data/spec/integration/schema/macros/when_spec.rb +28 -8
  95. data/spec/integration/schema/nested_values_spec.rb +11 -8
  96. data/spec/integration/schema/not_spec.rb +2 -2
  97. data/spec/integration/schema/numbers_spec.rb +1 -1
  98. data/spec/integration/schema/option_with_default_spec.rb +1 -1
  99. data/spec/integration/schema/predicate_verification_spec.rb +9 -0
  100. data/spec/integration/schema/predicates/array_spec.rb +295 -0
  101. data/spec/integration/schema/predicates/custom_spec.rb +103 -0
  102. data/spec/integration/schema/predicates/empty_spec.rb +263 -0
  103. data/spec/integration/schema/predicates/eql_spec.rb +327 -0
  104. data/spec/integration/schema/predicates/even_spec.rb +455 -0
  105. data/spec/integration/schema/predicates/excluded_from_spec.rb +455 -0
  106. data/spec/integration/schema/predicates/excludes_spec.rb +391 -0
  107. data/spec/integration/schema/predicates/filled_spec.rb +467 -0
  108. data/spec/integration/schema/predicates/format_spec.rb +455 -0
  109. data/spec/integration/schema/predicates/gt_spec.rb +519 -0
  110. data/spec/integration/schema/predicates/gteq_spec.rb +519 -0
  111. data/spec/integration/schema/predicates/hash_spec.rb +69 -0
  112. data/spec/integration/schema/predicates/included_in_spec.rb +455 -0
  113. data/spec/integration/schema/predicates/includes_spec.rb +391 -0
  114. data/spec/integration/schema/predicates/key_spec.rb +88 -0
  115. data/spec/integration/schema/predicates/lt_spec.rb +520 -0
  116. data/spec/integration/schema/predicates/lteq_spec.rb +519 -0
  117. data/spec/integration/schema/predicates/max_size_spec.rb +391 -0
  118. data/spec/integration/schema/predicates/min_size_spec.rb +391 -0
  119. data/spec/integration/schema/predicates/none_spec.rb +265 -0
  120. data/spec/integration/schema/predicates/not_eql_spec.rb +391 -0
  121. data/spec/integration/schema/predicates/odd_spec.rb +455 -0
  122. data/spec/integration/schema/predicates/size/fixed_spec.rb +401 -0
  123. data/spec/integration/schema/predicates/size/range_spec.rb +399 -0
  124. data/spec/integration/schema/predicates/type_spec.rb +391 -0
  125. data/spec/integration/schema/reusing_schema_spec.rb +4 -4
  126. data/spec/integration/schema/using_types_spec.rb +24 -6
  127. data/spec/integration/schema/xor_spec.rb +2 -2
  128. data/spec/integration/schema_builders_spec.rb +15 -0
  129. data/spec/integration/schema_spec.rb +37 -12
  130. data/spec/shared/predicate_helper.rb +13 -0
  131. data/spec/spec_helper.rb +10 -0
  132. data/spec/support/matchers.rb +38 -0
  133. data/spec/support/predicates_integration.rb +7 -0
  134. data/spec/unit/hint_compiler_spec.rb +10 -8
  135. data/spec/unit/input_processor_compiler/form_spec.rb +45 -43
  136. data/spec/unit/input_processor_compiler/json_spec.rb +37 -35
  137. data/spec/unit/predicate_registry_spec.rb +34 -0
  138. data/spec/unit/schema/key_spec.rb +12 -14
  139. data/spec/unit/schema/rule_spec.rb +4 -2
  140. data/spec/unit/schema/value_spec.rb +38 -121
  141. metadata +150 -16
  142. data/lib/dry/validation/schema/attr.rb +0 -15
  143. data/spec/integration/attr_spec.rb +0 -122
@@ -1,7 +1,7 @@
1
1
  RSpec.describe 'Schema / Injecting Rules' do
2
2
  subject(:schema) do
3
3
  Dry::Validation.Schema(rules: other.class.rules) do
4
- key(:email).maybe
4
+ required(:email).maybe
5
5
 
6
6
  rule(:email) { value(:login).true? > value(:email).filled? }
7
7
  end
@@ -9,7 +9,7 @@ RSpec.describe 'Schema / Injecting Rules' do
9
9
 
10
10
  let(:other) do
11
11
  Dry::Validation.Schema do
12
- key(:login) { |value| value.bool? }
12
+ required(:login) { |value| value.bool? }
13
13
  end
14
14
  end
15
15
 
@@ -15,7 +15,7 @@ RSpec.describe Dry::Validation, 'with localized messages' do
15
15
  config.messages = :i18n
16
16
  end
17
17
 
18
- key(:email) { |email| email.filled? }
18
+ required(:email) { |email| email.filled? }
19
19
  end
20
20
  end
21
21
 
@@ -38,7 +38,7 @@ RSpec.describe Dry::Validation, 'with localized messages' do
38
38
  end
39
39
  end
40
40
 
41
- key(:email) { |email| email.filled? }
41
+ required(:email) { |email| email.filled? }
42
42
  end
43
43
  end
44
44
 
@@ -30,13 +30,13 @@ RSpec.describe Messages::I18n do
30
30
  it 'returns a message for a specific val type' do
31
31
  message = messages[:size?, rule: :pages, val_type: String]
32
32
 
33
- expect(message).to eql("length must be %{num}")
33
+ expect(message).to eql("length must be %{size}")
34
34
  end
35
35
 
36
36
  it 'returns a message for a specific rule and its default arg type' do
37
37
  message = messages[:size?, rule: :pages]
38
38
 
39
- expect(message).to eql("size must be %{num}")
39
+ expect(message).to eql("size must be %{size}")
40
40
  end
41
41
 
42
42
  it 'returns a message for a specific rule and its arg type' do
@@ -62,7 +62,7 @@ RSpec.describe Messages::I18n do
62
62
  it 'returns a message for a specific rule and its default arg type' do
63
63
  message = messages[:size?, rule: :pages, locale: :pl]
64
64
 
65
- expect(message).to eql("wielkość musi być równa %{num}")
65
+ expect(message).to eql("wielkość musi być równa %{size}")
66
66
  end
67
67
 
68
68
  it 'returns a message for a specific rule and its arg type' do
@@ -4,9 +4,9 @@ RSpec.describe Dry::Validation::Schema do
4
4
  Dry::Validation.Schema do
5
5
  optional(:email) { |email| email.filled? }
6
6
 
7
- key(:address) do
8
- key(:city, &:filled?)
9
- key(:street, &:filled?)
7
+ required(:address).schema do
8
+ required(:city, &:filled?)
9
+ required(:street, &:filled?)
10
10
 
11
11
  optional(:phone_number) do
12
12
  none? | str?
@@ -1,23 +1,59 @@
1
1
  RSpec.describe Dry::Validation::Schema, 'for an array' do
2
- subject(:schema) do
3
- Dry::Validation.Schema do
4
- each do
5
- key(:prefix).required
6
- key(:value).required
2
+ context 'without type specs' do
3
+ subject(:schema) do
4
+ Dry::Validation.Schema do
5
+ each do
6
+ schema do
7
+ required(:prefix).filled
8
+ required(:value).filled
9
+ end
10
+ end
7
11
  end
8
12
  end
13
+
14
+ it 'applies its rules to array input' do
15
+ result = schema.([{ prefix: 1, value: 123 }, { prefix: 2, value: 456 }])
16
+
17
+ expect(result).to be_success
18
+
19
+ result = schema.([{ prefix: 1, value: nil }, { prefix: nil, value: 456 }])
20
+
21
+ expect(result.messages).to eql(
22
+ 0 => { value: ["must be filled"] },
23
+ 1 => { prefix: ["must be filled"] }
24
+ )
25
+ end
9
26
  end
10
27
 
11
- it 'applies its rules to array input' do
12
- result = schema.([{ prefix: 1, value: 123 }, { prefix: 2, value: 456 }])
28
+ context 'with type specs' do
29
+ subject(:schema) do
30
+ Dry::Validation.Form do
31
+ configure { config.type_specs = true }
13
32
 
14
- expect(result).to be_success
33
+ each do
34
+ schema do
35
+ required(:prefix, :int).filled
36
+ required(:value, :int).filled
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ it 'applies its rules to coerced array input' do
43
+ result = schema.([{ prefix: 1, value: '123' }, { prefix: 2, value: 456 }])
44
+
45
+ expect(result).to be_success
15
46
 
16
- result = schema.([{ prefix: 1, value: nil }, { prefix: nil, value: 456 }])
47
+ expect(result.output).to eql(
48
+ [{ prefix: 1, value: 123 }, { prefix: 2, value: 456 }]
49
+ )
17
50
 
18
- expect(result.messages).to eql(
19
- 0 => { value: ["must be filled"] },
20
- 1 => { prefix: ["must be filled"] }
21
- )
51
+ result = schema.([{ prefix: 1, value: nil }, { prefix: nil, value: 456 }])
52
+
53
+ expect(result.messages).to eql(
54
+ 0 => { value: ["must be filled"] },
55
+ 1 => { prefix: ["must be filled"] }
56
+ )
57
+ end
22
58
  end
23
59
  end
@@ -50,8 +50,8 @@ RSpec.describe Schema, 'using high-level rules' do
50
50
  end
51
51
  end
52
52
 
53
- key(:login).required(:bool?)
54
- key(:email).maybe
53
+ required(:login).filled(:bool?)
54
+ required(:email).maybe
55
55
 
56
56
  rule(:email_presence) { value(:login).true?.then(value(:email).filled?) }
57
57
 
@@ -83,18 +83,18 @@ RSpec.describe Schema, 'using high-level rules' do
83
83
  describe 'with nested schemas' do
84
84
  subject(:schema) do
85
85
  Dry::Validation.Schema do
86
- key(:command).required(:str?, inclusion?: %w(First Second))
86
+ required(:command).filled(:str?, included_in?: %w(First Second))
87
87
 
88
- key(:args).required(:hash?)
88
+ required(:args).filled(:hash?)
89
89
 
90
90
  rule(first_args: [:command, :args]) do |command, args|
91
91
  command.eql?('First')
92
- .then(args.schema { key(:first).required(:bool?) })
92
+ .then(args.schema { required(:first).filled(:bool?) })
93
93
  end
94
94
 
95
95
  rule(second_args: [:command, :args]) do |command, args|
96
96
  command.eql?('Second')
97
- .then(args.schema { key(:second).required(:bool?) })
97
+ .then(args.schema { required(:second).filled(:bool?) })
98
98
  end
99
99
  end
100
100
  end
@@ -1,9 +1,9 @@
1
1
  RSpec.describe 'Check depending on a nested value from a hash' do
2
2
  subject(:schema) do
3
3
  Dry::Validation.Schema do
4
- key(:tag).schema do
5
- key(:color).schema do
6
- key(:value).required(:str?)
4
+ required(:tag).schema do
5
+ required(:color).schema do
6
+ required(:value).filled(:str?)
7
7
  end
8
8
  end
9
9
 
@@ -1,7 +1,7 @@
1
1
  RSpec.describe 'Check depending on nth element in an array' do
2
2
  subject(:schema) do
3
3
  Dry::Validation.Schema do
4
- key(:tags).each(:str?)
4
+ required(:tags).each(:str?)
5
5
 
6
6
  rule(red: [[:tags, 0]]) do |value|
7
7
  value.eql?('red')
@@ -1,9 +1,9 @@
1
1
  RSpec.describe 'Schema with each and set rules' do
2
2
  subject(:schema) do
3
3
  Dry::Validation.Schema do
4
- key(:payments).each do
5
- key(:method).required(:str?)
6
- key(:amount).required(:float?)
4
+ required(:payments).each do
5
+ required(:method).filled(:str?)
6
+ required(:amount).filled(:float?)
7
7
  end
8
8
  end
9
9
  end
@@ -0,0 +1,27 @@
1
+ RSpec.describe 'Extending DSL' do
2
+ it 'allows configuring custom DSL methods' do
3
+ dsl_ext = Module.new do
4
+ def maybe_int(name, *predicates, &block)
5
+ required(name, [:nil, :int]).maybe(:int?, *predicates, &block)
6
+ end
7
+ end
8
+
9
+ Dry::Validation::Schema.configure do |config|
10
+ config.dsl_extensions = dsl_ext
11
+ end
12
+
13
+ schema = Dry::Validation.Schema do
14
+ configure do
15
+ config.input_processor = :form
16
+ config.type_specs = true
17
+ end
18
+
19
+ maybe_int(:age)
20
+ end
21
+
22
+ expect(schema.(age: nil)).to be_success
23
+ expect(schema.(age: 1)).to be_success
24
+ expect(schema.(age: '1')).to be_success
25
+ expect(schema.(age: 'foo').messages).to eql(age: ['must be an integer'])
26
+ end
27
+ end
@@ -0,0 +1,182 @@
1
+ RSpec.describe Dry::Validation::Schema::Form, 'explicit types' do
2
+ context 'single type spec without rules' do
3
+ subject(:schema) do
4
+ Dry::Validation.Form do
5
+ configure { config.type_specs = true }
6
+ required(:age, :int)
7
+ end
8
+ end
9
+
10
+ it 'uses form coercion' do
11
+ expect(schema.('age' => '19').to_h).to eql(age: 19)
12
+ end
13
+ end
14
+
15
+ context 'single type spec with rules' do
16
+ subject(:schema) do
17
+ Dry::Validation.Form do
18
+ configure { config.type_specs = true }
19
+ required(:age, :int).value(:int?, gt?: 18)
20
+ end
21
+ end
22
+
23
+ it 'applies rules to coerced value' do
24
+ expect(schema.(age: 19).messages).to be_empty
25
+ expect(schema.(age: 18).messages).to eql(age: ['must be greater than 18'])
26
+ end
27
+ end
28
+
29
+ context 'single type spec with an array' do
30
+ subject(:schema) do
31
+ Dry::Validation.Form do
32
+ configure { config.type_specs = true }
33
+ required(:nums, [:int])
34
+ end
35
+ end
36
+
37
+ it 'uses form coercion' do
38
+ expect(schema.(nums: %w(1 2 3)).to_h).to eql(nums: [1, 2, 3])
39
+ end
40
+ end
41
+
42
+ context 'sum type spec without rules' do
43
+ subject(:schema) do
44
+ Dry::Validation.Form do
45
+ configure { config.type_specs = true }
46
+ required(:age, [:nil, :int])
47
+ end
48
+ end
49
+
50
+ it 'uses form coercion' do
51
+ expect(schema.('age' => '19').to_h).to eql(age: 19)
52
+ expect(schema.('age' => '').to_h).to eql(age: nil)
53
+ end
54
+ end
55
+
56
+ context 'sum type spec with rules' do
57
+ subject(:schema) do
58
+ Dry::Validation.Form do
59
+ configure { config.type_specs = true }
60
+ required(:age, [:nil, :int]).maybe(:int?, gt?: 18)
61
+ end
62
+ end
63
+
64
+ it 'applies rules to coerced value' do
65
+ expect(schema.(age: nil).messages).to be_empty
66
+ expect(schema.(age: 19).messages).to be_empty
67
+ expect(schema.(age: 18).messages).to eql(age: ['must be greater than 18'])
68
+ end
69
+ end
70
+
71
+ context 'using a type object' do
72
+ subject(:schema) do
73
+ Dry::Validation.Form do
74
+ configure { config.type_specs = true }
75
+ required(:age, Types::Form::Nil | Types::Form::Int)
76
+ end
77
+ end
78
+
79
+ it 'uses form coercion' do
80
+ expect(schema.('age' => '').to_h).to eql(age: nil)
81
+ expect(schema.('age' => '19').to_h).to eql(age: 19)
82
+ end
83
+ end
84
+
85
+ context 'nested schema' do
86
+ subject(:schema) do
87
+ Dry::Validation.Form do
88
+ configure { config.type_specs = true }
89
+
90
+ required(:user).schema do
91
+ required(:email, :string)
92
+ required(:age, :int)
93
+
94
+ required(:address).schema do
95
+ required(:street, :string)
96
+ required(:city, :string)
97
+ required(:zipcode, :string)
98
+
99
+ required(:location).schema do
100
+ required(:lat, :float)
101
+ required(:lng, :float)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ it 'uses form coercion for nested input' do
109
+ input = {
110
+ 'user' => {
111
+ 'email' => 'jane@doe.org',
112
+ 'age' => '21',
113
+ 'address' => {
114
+ 'street' => 'Street 1',
115
+ 'city' => 'NYC',
116
+ 'zipcode' => '1234',
117
+ 'location' => { 'lat' => '1.23', 'lng' => '4.56' }
118
+ }
119
+ }
120
+ }
121
+
122
+ expect(schema.(input).to_h).to eql(
123
+ user: {
124
+ email: 'jane@doe.org',
125
+ age: 21,
126
+ address: {
127
+ street: 'Street 1',
128
+ city: 'NYC',
129
+ zipcode: '1234',
130
+ location: { lat: 1.23, lng: 4.56 }
131
+ }
132
+ }
133
+ )
134
+ end
135
+ end
136
+
137
+ context 'nested schema with arrays' do
138
+ subject(:schema) do
139
+ Dry::Validation.Form do
140
+ configure { config.type_specs = true }
141
+
142
+ required(:song).schema do
143
+ required(:title, :string)
144
+
145
+ required(:tags).each do
146
+ schema do
147
+ required(:name, :string)
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ it 'fails to coerce gracefuly' do
155
+ result = schema.(song: nil)
156
+
157
+ expect(result.messages).to eql(song: ['must be a hash'])
158
+ expect(result.to_h).to eql(song: nil)
159
+
160
+ result = schema.(song: { tags: nil })
161
+
162
+ expect(result.messages).to eql(song: { tags: ['must be an array'] })
163
+ expect(result.to_h).to eql(song: { tags: nil })
164
+ end
165
+
166
+ it 'uses form coercion for nested input' do
167
+ input = {
168
+ 'song' => {
169
+ 'title' => 'dry-rb is awesome lala',
170
+ 'tags' => [{ 'name' => 'red' }, { 'name' => 'blue' }]
171
+ }
172
+ }
173
+
174
+ expect(schema.(input).to_h).to eql(
175
+ song: {
176
+ title: 'dry-rb is awesome lala',
177
+ tags: [{ name: 'red' }, { name: 'blue' }]
178
+ }
179
+ )
180
+ end
181
+ end
182
+ end
@@ -1,17 +1,23 @@
1
1
  RSpec.describe Dry::Validation::Schema::Form, 'defining a schema' do
2
2
  subject(:schema) do
3
3
  Dry::Validation.Form do
4
- key(:email).required
4
+ configure do
5
+ def email?(value)
6
+ true
7
+ end
8
+ end
9
+
10
+ required(:email).filled
5
11
 
6
- key(:age).maybe(:int?, gt?: 18)
12
+ required(:age).maybe(:int?, gt?: 18)
7
13
 
8
- key(:address).schema do
9
- key(:city).required
10
- key(:street).required
14
+ required(:address).schema do
15
+ required(:city).filled
16
+ required(:street).filled
11
17
 
12
- key(:loc).schema do
13
- key(:lat).required(:float?)
14
- key(:lng).required(:float?)
18
+ required(:loc).schema do
19
+ required(:lat).filled(:float?)
20
+ required(:lng).filled(:float?)
15
21
  end
16
22
  end
17
23
 
@@ -20,12 +26,6 @@ RSpec.describe Dry::Validation::Schema::Form, 'defining a schema' do
20
26
  optional(:phone_number).maybe(:int?, gt?: 0)
21
27
 
22
28
  rule(:email_valid) { value(:email).email? }
23
-
24
- configure do
25
- def email?(value)
26
- true
27
- end
28
- end
29
29
  end
30
30
  end
31
31
 
@@ -118,12 +118,84 @@ RSpec.describe Dry::Validation::Schema::Form, 'defining a schema' do
118
118
  end
119
119
  end
120
120
 
121
+ describe 'with an each and nested schema' do
122
+ subject(:schema) do
123
+ Dry::Validation.Form do
124
+ required(:items).each do
125
+ schema do
126
+ required(:title).filled(:str?)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ it 'passes when each element passes schema check' do
133
+ expect(schema.(items: [{ title: 'Foo' }])).to be_success
134
+ end
135
+
136
+ it 'fails when one or more elements did not pass schema check' do
137
+ expect(schema.(items: [{ title: 'Foo' }, { title: :Foo }]).messages).to eql(
138
+ items: { 1 => { title: ['must be a string'] } }
139
+ )
140
+ end
141
+ end
142
+
143
+ describe 'symbolizing keys when coercion fails' do
144
+ subject(:schema) do
145
+ Dry::Validation.Form do
146
+ required(:email).value(size?: 8..60)
147
+ required(:birthdate).value(:date?)
148
+ required(:age).value(:int?, gt?: 23)
149
+ required(:tags).maybe(max_size?: 3)
150
+ end
151
+ end
152
+
153
+ let(:tags) { %(al b c) }
154
+
155
+ it 'symbolizes keys and coerces values' do
156
+ result = schema.(
157
+ 'email' => 'jane@doe.org',
158
+ 'birthdate' => '2001-02-03',
159
+ 'age' => '24',
160
+ 'tags' => tags
161
+ ).to_h
162
+
163
+ expect(result.to_h).to eql(
164
+ email: 'jane@doe.org',
165
+ birthdate: Date.new(2001, 2, 3),
166
+ age: 24,
167
+ tags: tags
168
+ )
169
+ end
170
+
171
+ it 'symbolizes keys even when coercion fails' do
172
+ result = schema.(
173
+ 'email' => 'jane@doe.org',
174
+ 'birthdate' => '2001-sutin-03',
175
+ 'age' => ['oops'],
176
+ 'tags' => nil
177
+ )
178
+
179
+ expect(result.to_h).to eql(
180
+ email: 'jane@doe.org',
181
+ birthdate: '2001-sutin-03',
182
+ age: ['oops'],
183
+ tags: nil
184
+ )
185
+
186
+ expect(result.messages).to eql(
187
+ birthdate: ['must be a date'],
188
+ age: ['must be an integer', 'must be greater than 23']
189
+ )
190
+ end
191
+ end
192
+
121
193
  describe 'with nested schema in a high-level rule' do
122
194
  subject(:schema) do
123
195
  Dry::Validation.Form do
124
- key(:address).maybe(:hash?)
196
+ required(:address).maybe(:hash?)
125
197
 
126
- key(:delivery).required(:bool?)
198
+ required(:delivery).filled(:bool?)
127
199
 
128
200
  rule(address: [:delivery, :address]) do |delivery, address|
129
201
  delivery.true?.then(address.schema(AddressSchema))
@@ -133,8 +205,8 @@ RSpec.describe Dry::Validation::Schema::Form, 'defining a schema' do
133
205
 
134
206
  before do
135
207
  AddressSchema = Dry::Validation.Form do
136
- key(:city).required
137
- key(:zipcode).required(:int?)
208
+ required(:city).filled
209
+ required(:zipcode).filled(:int?)
138
210
  end
139
211
  end
140
212