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