dry-validation 0.7.4 → 0.8.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 (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
@@ -0,0 +1,47 @@
1
+ RSpec.describe Dry::Validation::Schema, 'for a hash' do
2
+ context 'without type specs' do
3
+ subject(:schema) do
4
+ Dry::Validation.Schema do
5
+ hash? do
6
+ required(:prefix).filled
7
+ required(:value).filled
8
+ end
9
+ end
10
+ end
11
+
12
+ it 'applies its rules to array input' do
13
+ result = schema.(prefix: 1, value: 123)
14
+
15
+ expect(result).to be_success
16
+
17
+ result = schema.(prefix: 1, value: nil)
18
+
19
+ expect(result.messages).to eql(value: ["must be filled"])
20
+ end
21
+ end
22
+
23
+ context 'with type specs' do
24
+ subject(:schema) do
25
+ Dry::Validation.Form do
26
+ configure { config.type_specs = true }
27
+
28
+ hash? do
29
+ required(:prefix, :int).filled
30
+ required(:value, :int).filled
31
+ end
32
+ end
33
+ end
34
+
35
+ it 'applies its rules to coerced array input' do
36
+ result = schema.(prefix: 1, value: '123')
37
+
38
+ expect(result).to be_success
39
+
40
+ expect(result.output).to eql(prefix: 1, value: 123)
41
+
42
+ result = schema.(prefix: 1, value: nil)
43
+
44
+ expect(result.messages).to eql(value: ["must be filled"])
45
+ end
46
+ end
47
+ end
@@ -1,16 +1,16 @@
1
1
  RSpec.describe 'Inheriting schema' do
2
2
  subject(:schema) do
3
3
  Dry::Validation.Schema(base_schema) do
4
- key(:location).schema do
5
- key(:lat).required(:float?)
6
- key(:lng).required(:float?)
4
+ required(:location).schema do
5
+ required(:lat).filled(:float?)
6
+ required(:lng).filled(:float?)
7
7
  end
8
8
  end
9
9
  end
10
10
 
11
11
  let(:base_schema) do
12
12
  Dry::Validation.Schema do
13
- key(:city).required
13
+ required(:city).filled
14
14
  end
15
15
  end
16
16
 
@@ -5,18 +5,18 @@ RSpec.describe Dry::Validation::Schema, 'setting input processor in schema' do
5
5
  config.input_processor = :sanitizer
6
6
  end
7
7
 
8
- key(:email).required
8
+ required(:email).filled
9
9
 
10
- key(:age).maybe(:int?, gt?: 18)
10
+ required(:age).maybe(:int?, gt?: 18)
11
11
 
12
- key(:address).schema do
13
- key(:city).required
14
- key(:street).required
12
+ required(:address).schema do
13
+ required(:city).filled
14
+ required(:street).filled
15
15
  end
16
16
 
17
- key(:phone_numbers).each do
18
- key(:prefix).required
19
- key(:value).required
17
+ required(:phone_numbers).each do
18
+ required(:prefix).filled
19
+ required(:value).filled
20
20
  end
21
21
  end
22
22
  end
@@ -0,0 +1,157 @@
1
+ RSpec.describe Dry::Validation::Schema::JSON, 'explicit types' do
2
+ context 'single type spec without rules' do
3
+ subject(:schema) do
4
+ Dry::Validation.JSON do
5
+ configure { config.type_specs = true }
6
+ required(:bdate, :date)
7
+ end
8
+ end
9
+
10
+ it 'uses json coercion' do
11
+ expect(schema.('bdate' => '2010-09-08').to_h).to eql(bdate: Date.new(2010, 9, 8))
12
+ end
13
+ end
14
+
15
+ context 'single type spec with rules' do
16
+ subject(:schema) do
17
+ Dry::Validation.JSON do
18
+ configure { config.type_specs = true }
19
+ required(:bdate, :date).value(:date?, gt?: Date.new(2009))
20
+ end
21
+ end
22
+
23
+ it 'applies rules to coerced value' do
24
+ expect(schema.(bdate: "2010-09-07").messages).to be_empty
25
+ expect(schema.(bdate: "2008-01-01").messages).to eql(bdate: ['must be greater than 2009-01-01'])
26
+ end
27
+ end
28
+
29
+ context 'sum type spec without rules' do
30
+ subject(:schema) do
31
+ Dry::Validation.JSON do
32
+ configure { config.type_specs = true }
33
+ required(:bdate, [:nil, :date])
34
+ end
35
+ end
36
+
37
+ it 'uses form coercion' do
38
+ expect(schema.('bdate' => '2010-09-08').to_h).to eql(bdate: Date.new(2010, 9, 8))
39
+ expect(schema.('bdate' => '').to_h).to eql(bdate: nil)
40
+ end
41
+ end
42
+
43
+ context 'sum type spec with rules' do
44
+ subject(:schema) do
45
+ Dry::Validation.JSON do
46
+ configure { config.type_specs = true }
47
+ required(:bdate, [:nil, :date]).maybe(:date?, gt?: Date.new(2008))
48
+ end
49
+ end
50
+
51
+ it 'applies rules to coerced value' do
52
+ expect(schema.(bdate: nil).messages).to be_empty
53
+ expect(schema.(bdate: "2010-09-07").messages).to be_empty
54
+ expect(schema.(bdate: "2008-01-01").messages).to eql(bdate: ['must be greater than 2008-01-01'])
55
+ end
56
+ end
57
+
58
+ context 'using a type object' do
59
+ subject(:schema) do
60
+ Dry::Validation.JSON do
61
+ configure { config.type_specs = true }
62
+ required(:bdate, Types::Json::Nil | Types::Json::Date)
63
+ end
64
+ end
65
+
66
+ it 'uses form coercion' do
67
+ expect(schema.('bdate' => '').to_h).to eql(bdate: nil)
68
+ expect(schema.('bdate' => '2010-09-08').to_h).to eql(bdate: Date.new(2010, 9, 8))
69
+ end
70
+ end
71
+
72
+ context 'nested schema' do
73
+ subject(:schema) do
74
+ Dry::Validation.JSON do
75
+ configure { config.type_specs = true }
76
+
77
+ required(:user).schema do
78
+ required(:email, :string)
79
+ required(:bdate, :date)
80
+
81
+ required(:address).schema do
82
+ required(:street, :string)
83
+ required(:city, :string)
84
+ required(:zipcode, :string)
85
+
86
+ required(:location).schema do
87
+ required(:lat, :float)
88
+ required(:lng, :float)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ it 'uses form coercion for nested input' do
96
+ input = {
97
+ 'user' => {
98
+ 'email' => 'jane@doe.org',
99
+ 'bdate' => '2010-09-08',
100
+ 'address' => {
101
+ 'street' => 'Street 1',
102
+ 'city' => 'NYC',
103
+ 'zipcode' => '1234',
104
+ 'location' => { 'lat' => 1.23, 'lng' => 4.56 }
105
+ }
106
+ }
107
+ }
108
+
109
+ expect(schema.(input).to_h).to eql(
110
+ user: {
111
+ email: 'jane@doe.org',
112
+ bdate: Date.new(2010, 9, 8),
113
+ address: {
114
+ street: 'Street 1',
115
+ city: 'NYC',
116
+ zipcode: '1234',
117
+ location: { lat: 1.23, lng: 4.56 }
118
+ }
119
+ }
120
+ )
121
+ end
122
+ end
123
+
124
+ context 'nested schema with arrays' do
125
+ subject(:schema) do
126
+ Dry::Validation.JSON do
127
+ configure { config.type_specs = true }
128
+
129
+ required(:song).schema do
130
+ required(:title, :string)
131
+
132
+ required(:tags).each do
133
+ schema do
134
+ required(:name, :string)
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ it 'uses form coercion for nested input' do
142
+ input = {
143
+ 'song' => {
144
+ 'title' => 'dry-rb is awesome lala',
145
+ 'tags' => [{ 'name' => 'red' }, { 'name' => 'blue' }]
146
+ }
147
+ }
148
+
149
+ expect(schema.(input).to_h).to eql(
150
+ song: {
151
+ title: 'dry-rb is awesome lala',
152
+ tags: [{ name: 'red' }, { name: 'blue' }]
153
+ }
154
+ )
155
+ end
156
+ end
157
+ end
@@ -1,17 +1,23 @@
1
1
  RSpec.describe Dry::Validation::Schema::JSON, 'defining a schema' do
2
2
  subject(:schema) do
3
3
  Dry::Validation.JSON 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::JSON, '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
 
@@ -121,9 +121,9 @@ RSpec.describe Dry::Validation::Schema::JSON, 'defining a schema' do
121
121
  describe 'with nested schema in a high-level rule' do
122
122
  subject(:schema) do
123
123
  Dry::Validation.JSON do
124
- key(:address).maybe(:hash?)
124
+ required(:address).maybe(:hash?)
125
125
 
126
- key(:delivery).required(:bool?)
126
+ required(:delivery).filled(:bool?)
127
127
 
128
128
  rule(address: [:delivery, :address]) do |delivery, address|
129
129
  delivery.true?.then(address.schema(AddressSchema))
@@ -133,8 +133,8 @@ RSpec.describe Dry::Validation::Schema::JSON, 'defining a schema' do
133
133
 
134
134
  before do
135
135
  AddressSchema = Dry::Validation.JSON do
136
- key(:city).required
137
- key(:zipcode).required(:int?)
136
+ required(:city).filled
137
+ required(:zipcode).filled(:int?)
138
138
  end
139
139
  end
140
140
 
@@ -12,7 +12,7 @@ RSpec.describe 'Macros #confirmation' do
12
12
  end
13
13
  end
14
14
 
15
- key(:password).maybe(min_size?: 3).confirmation
15
+ required(:password).maybe(min_size?: 3).confirmation
16
16
  end
17
17
  end
18
18
 
@@ -1,43 +1,175 @@
1
1
  RSpec.describe 'Macros #each' do
2
- context 'with a block' do
3
- context 'with a nested key' do
4
- subject(:schema) do
5
- Dry::Validation.Schema do
6
- key(:songs).each do
7
- key(:title).required
8
- end
9
- end
2
+ context "predicate without options" do
3
+ subject(:schema) do
4
+ Dry::Validation.Schema do
5
+ required(:foo).each(:filled?, :str?)
10
6
  end
7
+ end
11
8
 
12
- it 'passes when all elements are valid' do
13
- songs = [{ title: 'Hello' }, { title: 'World' }]
9
+ context 'with valid input' do
10
+ let(:input) { { foo: %w(a b c) } }
14
11
 
15
- expect(schema.(songs: songs)).to be_success
12
+ it 'is successful' do
13
+ expect(result).to be_successful
16
14
  end
15
+ end
17
16
 
18
- it 'fails when value is not an array' do
19
- expect(schema.(songs: 'oops').messages).to eql(songs: ['must be an array'])
17
+ context 'with invalid input' do
18
+ let(:input) { { foo: [[1, 2], "", "foo"] } }
19
+
20
+ it 'is not successful' do
21
+ expect(result).to be_failing(0 => ["must be a string"], 1 => ["must be filled"])
20
22
  end
23
+ end
21
24
 
22
- it 'fails when not all elements are valid' do
23
- songs = [{ title: 'Hello' }, { title: nil }, { title: nil }]
25
+ context 'with invalid input type' do
26
+ let(:input) { { foo: nil } }
24
27
 
25
- expect(schema.(songs: songs).messages).to eql(
26
- songs: {
27
- 1 => { title: ['must be filled'] },
28
- 2 => { title: ['must be filled'] }
29
- }
30
- )
28
+ it 'is not successful' do
29
+ expect(result).to be_failing ["must be an array"]
30
+ end
31
+ end
32
+ end
33
+
34
+ context "predicate with options" do
35
+ subject(:schema) do
36
+ Dry::Validation.Schema do
37
+ required(:foo).each(size?: 3)
31
38
  end
32
39
  end
33
40
 
41
+ context 'with valid input' do
42
+ let(:input) { { foo: [[1,2,3], "foo"] } }
43
+
44
+ it 'is successful' do
45
+ expect(result).to be_successful
46
+ end
47
+ end
48
+
49
+ context 'with invalid input' do
50
+ let(:input) { { foo: [[1,2], "foo"] } }
51
+
52
+ it 'is not successful' do
53
+ expect(result).to be_failing({0=>["size must be 3"]})
54
+ end
55
+ end
56
+
57
+ context 'with invalid input type' do
58
+ let(:input) { { foo: nil } }
59
+
60
+ it 'is not successful' do
61
+ expect(result).to be_failing ["must be an array", "size must be 3"]
62
+ end
63
+ end
64
+ end
65
+
66
+ context 'with filled macro' do
67
+ subject(:schema) do
68
+ Dry::Validation.Schema do
69
+ required(:foo).filled(size?: 2) { each(:str?) }
70
+ end
71
+ end
72
+
73
+ context 'with valid input' do
74
+ let(:input) { { foo: %w(a b) } }
75
+
76
+ it 'is successful' do
77
+ expect(result).to be_successful
78
+ end
79
+ end
80
+
81
+ context 'when value is not valid' do
82
+ let(:input) { { foo: ["foo"] } }
83
+
84
+ it 'is not successful' do
85
+ expect(result).to be_failing(["size must be 2"])
86
+ end
87
+ end
88
+
89
+ context 'when value has invalid elements' do
90
+ let(:input) { { foo: [:foo, "foo"] } }
91
+
92
+ it 'is not successful' do
93
+ expect(result).to be_failing(0 => ["must be a string"])
94
+ end
95
+ end
96
+ end
97
+
98
+ context 'with maybe macro' do
99
+ subject(:schema) do
100
+ Dry::Validation.Schema do
101
+ required(:foo).maybe { each(:str?) }
102
+ end
103
+ end
104
+
105
+ context 'with nil input' do
106
+ let(:input) { { foo: nil } }
107
+
108
+ it 'is successful' do
109
+ expect(result).to be_successful
110
+ end
111
+ end
112
+
113
+ context 'with valid input' do
114
+ let(:input) { { foo: %w(a b c) } }
115
+
116
+ it 'is successful' do
117
+ expect(result).to be_successful
118
+ end
119
+ end
120
+
121
+ context 'with invalid input' do
122
+ let(:input) { { foo: [:foo, "foo"] } }
123
+
124
+ it 'is not successful' do
125
+ expect(result).to be_failing(0 => ["must be a string"])
126
+ end
127
+ end
128
+ end
129
+
130
+ context 'with external schema macro' do
131
+ subject(:schema) do
132
+ Dry::Validation.Schema do
133
+ required(:foo).each(FooSchema)
134
+ end
135
+ end
136
+
137
+ before do
138
+ FooSchema = Dry::Validation.Schema do
139
+ required(:bar).filled(:str?)
140
+ end
141
+ end
142
+
143
+ after do
144
+ Object.send(:remove_const, :FooSchema)
145
+ end
146
+
147
+ context 'with valid input' do
148
+ let(:input) { { foo: [{ bar: "baz" }] } }
149
+
150
+ it 'is successful' do
151
+ expect(result).to be_successful
152
+ end
153
+ end
154
+
155
+ context 'when value is not valid' do
156
+ let(:input) { { foo: [{ bar: 1 }] } }
157
+
158
+ it 'is not successful' do
159
+ # FIXME: our be_failing didn't work with such nested wow hash
160
+ expect(result.messages).to eql(foo: { 0 => { bar: ["must be a string"] } })
161
+ end
162
+ end
163
+ end
164
+
165
+ context 'with a block' do
34
166
  context 'with a nested schema' do
35
167
  subject(:schema) do
36
168
  Dry::Validation.Schema do
37
- key(:songs).each do
169
+ required(:songs).each do
38
170
  schema do
39
- key(:title).required
40
- key(:author).required
171
+ required(:title).filled
172
+ required(:author).filled
41
173
  end
42
174
  end
43
175
  end
@@ -76,31 +208,33 @@ RSpec.describe 'Macros #each' do
76
208
  end
77
209
 
78
210
  context 'with inferred predicates and a form schema' do
79
- subject(:schema) do
80
- Dry::Validation.Form do
81
- key(:songs).each(:str?)
211
+ context "predicate w/o options" do
212
+ subject(:schema) do
213
+ Dry::Validation.Form do
214
+ required(:songs).each(:str?)
215
+ end
82
216
  end
83
- end
84
217
 
85
- it 'passes when all elements are valid' do
86
- songs = %w(hello world)
218
+ it 'passes when all elements are valid' do
219
+ songs = %w(hello world)
87
220
 
88
- expect(schema.(songs: songs)).to be_success
89
- end
221
+ expect(schema.(songs: songs)).to be_success
222
+ end
90
223
 
91
- it 'fails when value is not an array' do
92
- expect(schema.(songs: 'oops').messages).to eql(songs: ['must be an array'])
93
- end
224
+ it 'fails when value is not an array' do
225
+ expect(schema.(songs: 'oops').messages).to eql(songs: ['must be an array'])
226
+ end
94
227
 
95
- it 'fails when not all elements are valid' do
96
- songs = ['hello', nil, 2]
228
+ it 'fails when not all elements are valid' do
229
+ songs = ['hello', nil, 2]
97
230
 
98
- expect(schema.(songs: songs).messages).to eql(
99
- songs: {
100
- 1 => ['must be a string'],
101
- 2 => ['must be a string']
102
- }
103
- )
231
+ expect(schema.(songs: songs).messages).to eql(
232
+ songs: {
233
+ 1 => ['must be a string'],
234
+ 2 => ['must be a string']
235
+ }
236
+ )
237
+ end
104
238
  end
105
239
  end
106
240
  end