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