dry-validation 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +23 -0
- data/README.md +203 -26
- data/config/errors.yml +16 -0
- data/dry-validation.gemspec +1 -0
- data/examples/each.rb +19 -0
- data/examples/form.rb +15 -0
- data/examples/nested.rb +13 -11
- data/lib/dry/validation.rb +5 -0
- data/lib/dry/validation/error_compiler.rb +2 -18
- data/lib/dry/validation/input_type_compiler.rb +78 -0
- data/lib/dry/validation/messages.rb +1 -7
- data/lib/dry/validation/predicates.rb +33 -1
- data/lib/dry/validation/result.rb +8 -0
- data/lib/dry/validation/rule.rb +11 -90
- data/lib/dry/validation/rule/composite.rb +50 -0
- data/lib/dry/validation/rule/each.rb +13 -0
- data/lib/dry/validation/rule/key.rb +17 -0
- data/lib/dry/validation/rule/set.rb +22 -0
- data/lib/dry/validation/rule/value.rb +13 -0
- data/lib/dry/validation/rule_compiler.rb +5 -0
- data/lib/dry/validation/schema.rb +6 -2
- data/lib/dry/validation/schema/definition.rb +4 -0
- data/lib/dry/validation/schema/form.rb +19 -0
- data/lib/dry/validation/schema/key.rb +16 -3
- data/lib/dry/validation/schema/result.rb +29 -0
- data/lib/dry/validation/schema/rule.rb +14 -16
- data/lib/dry/validation/schema/value.rb +14 -2
- data/lib/dry/validation/version.rb +1 -1
- data/spec/integration/custom_error_messages_spec.rb +1 -1
- data/spec/integration/optional_keys_spec.rb +30 -0
- data/spec/integration/schema_form_spec.rb +99 -0
- data/spec/integration/{validation_spec.rb → schema_spec.rb} +40 -13
- data/spec/shared/predicates.rb +1 -1
- data/spec/unit/error_compiler_spec.rb +64 -0
- data/spec/unit/input_type_compiler_spec.rb +205 -0
- data/spec/unit/predicates/bool_spec.rb +34 -0
- data/spec/unit/predicates/date_spec.rb +31 -0
- data/spec/unit/predicates/date_time_spec.rb +31 -0
- data/spec/unit/predicates/decimal_spec.rb +32 -0
- data/spec/unit/predicates/float_spec.rb +31 -0
- data/spec/unit/predicates/{nil_spec.rb → none_spec.rb} +2 -2
- data/spec/unit/predicates/time_spec.rb +31 -0
- data/spec/unit/rule/conjunction_spec.rb +28 -0
- data/spec/unit/rule/disjunction_spec.rb +36 -0
- data/spec/unit/rule/implication_spec.rb +14 -0
- data/spec/unit/rule/value_spec.rb +1 -1
- metadata +60 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee26d9a3fbca2ae3faf18827ad28f02bd323239b
|
4
|
+
data.tar.gz: a007eccefbc3456f8a481549512896074dcf3f86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe7c8c42376862ea8fbe14d3563ad65f7e3bc3f8d9b8bbbb9bff2fc239b7e16bfb9a11fd6cba5f2d0957650cd365470beaacb158a5a078998a589a15d92ca571
|
7
|
+
data.tar.gz: 8d337dd7bbf3fef8ca79f41ea962bc692ac6dd25b6fd93fca7c55559f0f3723f37e1bdd5fea804d3fcccbda1358f0360c8576268ecf766eed589208ac4771a31
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
# v0.2.0 to-be-released
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* `Schema::Form` with a built-in coercer inferred from type-check predicates (solnic)
|
6
|
+
* Ability to pass a block to predicate check in the DSL ie `value.hash? { ... }` (solnic)
|
7
|
+
* Optional keys using `option(:key_name) { ... }` interface in the DSL (solnic)
|
8
|
+
* New predicates:
|
9
|
+
- `bool?`
|
10
|
+
- `date?`
|
11
|
+
- `date_time?`
|
12
|
+
- `time?`
|
13
|
+
- `float?`
|
14
|
+
- `decimal?`
|
15
|
+
- `hash?`
|
16
|
+
- `array?`
|
17
|
+
|
18
|
+
### Fixed
|
19
|
+
|
20
|
+
* Added missing `and` / `or` interfaces to composite rules (solnic)
|
21
|
+
|
22
|
+
[Compare v0.1.0...HEAD](https://github.com/dryrb/dry-validation/compare/v0.1.0...HEAD)
|
23
|
+
|
1
24
|
# v0.1.0 2015-11-25
|
2
25
|
|
3
26
|
First public release
|
data/README.md
CHANGED
@@ -1,10 +1,18 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
[gem]: https://rubygems.org/gems/dry-validation
|
2
|
+
[travis]: https://travis-ci.org/dryrb/dry-validation
|
3
|
+
[gemnasium]: https://gemnasium.com/dryrb/dry-validation
|
4
|
+
[codeclimate]: https://codeclimate.com/github/dryrb/dry-validation
|
5
|
+
[coveralls]: https://coveralls.io/r/dryrb/dry-validation
|
6
|
+
[inchpages]: http://inch-ci.org/github/dryrb/dry-validation
|
7
|
+
|
8
|
+
# dry-validation [](https://gitter.im/dryrb/chat)
|
9
|
+
|
10
|
+
[][gem]
|
11
|
+
[][travis]
|
12
|
+
[][gemnasium]
|
13
|
+
[][codeclimate]
|
14
|
+
[][codeclimate]
|
15
|
+
[][inchpages]
|
8
16
|
|
9
17
|
Data validation library based on predicate logic and rule composition.
|
10
18
|
|
@@ -87,6 +95,84 @@ A couple of remarks:
|
|
87
95
|
* Schema object does not carry the input as its state, nor does it know how to access the input values, we
|
88
96
|
pass the input to `call` and get error set as the response
|
89
97
|
|
98
|
+
### Optional Keys
|
99
|
+
|
100
|
+
You can define which keys are optional and define rules for their values:
|
101
|
+
|
102
|
+
``` ruby
|
103
|
+
require 'dry-validation'
|
104
|
+
|
105
|
+
class Schema < Dry::Validation::Schema
|
106
|
+
key(:email) { |email| email.filled? }
|
107
|
+
|
108
|
+
optional(:age) do |age|
|
109
|
+
age.int? & age.gt?(18)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
schema = Schema.new
|
114
|
+
|
115
|
+
errors = schema.messages(email: 'jane@doe.org')
|
116
|
+
|
117
|
+
puts errors.inspect
|
118
|
+
# []
|
119
|
+
|
120
|
+
errors = schema.messages(email: 'jane@doe.org', age: 17)
|
121
|
+
|
122
|
+
puts errors.inspect
|
123
|
+
# [[:age, ["age must be greater than 18 (17 was given)"]]]
|
124
|
+
```
|
125
|
+
|
126
|
+
### Optional Values
|
127
|
+
|
128
|
+
When it is valid for a given value to be `nil` you can use `none?` predicate:
|
129
|
+
|
130
|
+
``` ruby
|
131
|
+
require 'dry-validation'
|
132
|
+
|
133
|
+
class Schema < Dry::Validation::Schema
|
134
|
+
key(:email) { |email| email.filled? }
|
135
|
+
|
136
|
+
key(:age) do |age|
|
137
|
+
age.none? | (age.int? & age.gt?(18))
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
schema = Schema.new
|
142
|
+
|
143
|
+
errors = schema.messages(email: 'jane@doe.org', age: nil)
|
144
|
+
|
145
|
+
puts errors.inspect
|
146
|
+
# []
|
147
|
+
|
148
|
+
errors = schema.messages(email: 'jane@doe.org', age: 19)
|
149
|
+
|
150
|
+
puts errors.inspect
|
151
|
+
# []
|
152
|
+
|
153
|
+
errors = schema.messages(email: 'jane@doe.org', age: 17)
|
154
|
+
|
155
|
+
puts errors.inspect
|
156
|
+
# [[:age, ["age must be greater than 18 (17 was given)"]]]
|
157
|
+
```
|
158
|
+
|
159
|
+
### Optional Key vs Value
|
160
|
+
|
161
|
+
We make a clear distinction between specifying an optional `key` and an optional
|
162
|
+
`value`. This gives you a way of being very specific about validation rules. You
|
163
|
+
can define a schema which can give you precise errors when a key was missing or
|
164
|
+
key was present but the value was nil.
|
165
|
+
|
166
|
+
This also comes with the benefit of being explicit about the type expectation.
|
167
|
+
In the example above we explicitly state that `:age` *can be nil* or it *can be an integer*
|
168
|
+
and when it *is an integer* we specify that it *must be greater than 18*.
|
169
|
+
|
170
|
+
Another benefit is that we can infer specific coercion rules when types are specified.
|
171
|
+
In example [`Schema::Form`](https://github.com/dryrb/dry-validation#form-validation-with-coercions)
|
172
|
+
will use `form.nil` type from dry-data to coerce empty strings into `nil` for you
|
173
|
+
whenever you specify `value.none? | value.int?`. Furthermore it will try to coerce
|
174
|
+
to `int` since that is our type expectation.
|
175
|
+
|
90
176
|
### Nested Hash
|
91
177
|
|
92
178
|
We are free to define validations for anything, including deeply nested structures:
|
@@ -96,17 +182,19 @@ require 'dry-validation'
|
|
96
182
|
|
97
183
|
class Schema < Dry::Validation::Schema
|
98
184
|
key(:address) do |address|
|
99
|
-
address.
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
street
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
185
|
+
address.hash? do
|
186
|
+
address.key(:city) do |city|
|
187
|
+
city.min_size?(3)
|
188
|
+
end
|
189
|
+
|
190
|
+
address.key(:street) do |street|
|
191
|
+
street.filled?
|
192
|
+
end
|
193
|
+
|
194
|
+
address.key(:country) do |country|
|
195
|
+
country.key(:name, &:filled?)
|
196
|
+
country.key(:code, &:filled?)
|
197
|
+
end
|
110
198
|
end
|
111
199
|
end
|
112
200
|
end
|
@@ -124,6 +212,74 @@ puts errors.inspect
|
|
124
212
|
# [[:address, [[:street, ["street is missing"]], [:country, ["country is missing"]]]]]
|
125
213
|
```
|
126
214
|
|
215
|
+
### Array Elements
|
216
|
+
|
217
|
+
You can use `each` rule for validating each element in an array:
|
218
|
+
|
219
|
+
``` ruby
|
220
|
+
class Schema < Dry::Validation::Schema
|
221
|
+
key(:phone_numbers) do |phone_numbers|
|
222
|
+
phone_numbers.array? do
|
223
|
+
phone_numbers.each(&:str?)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
schema = Schema.new
|
229
|
+
|
230
|
+
errors = schema.messages(phone_numbers: '')
|
231
|
+
|
232
|
+
puts errors.inspect
|
233
|
+
# [[:phone_numbers, ["phone_numbers must be an array"]]]
|
234
|
+
|
235
|
+
errors = schema.messages(phone_numbers: ['123456789', 123456789])
|
236
|
+
|
237
|
+
puts errors.inspect
|
238
|
+
# [[:phone_numbers, [[:phone_numbers, ["phone_numbers must be a string"]]]]]
|
239
|
+
```
|
240
|
+
|
241
|
+
### Form Validation With Coercions
|
242
|
+
|
243
|
+
Probably the most common use case is to validate form params. This is a special
|
244
|
+
kind of a validation for a couple of reasons:
|
245
|
+
|
246
|
+
* The input is a hash with stringified keys
|
247
|
+
* The input include values that are strings, hashes or arrays
|
248
|
+
* Prior validation, we need to coerce values and symbolize keys based on the
|
249
|
+
information from rules
|
250
|
+
|
251
|
+
For that reason, `dry-validation` ships with `Schema::Form` class:
|
252
|
+
|
253
|
+
``` ruby
|
254
|
+
require 'dry-validation'
|
255
|
+
require 'dry/validation/schema/form'
|
256
|
+
|
257
|
+
class UserFormSchema < Dry::Validation::Schema::Form
|
258
|
+
key(:email) { |value| value.str? & value.filled? }
|
259
|
+
|
260
|
+
key(:age) { |value| value.int? & value.gt?(18) }
|
261
|
+
end
|
262
|
+
|
263
|
+
schema = UserFormSchema.new
|
264
|
+
|
265
|
+
errors = schema.messages('email' => '', 'age' => '18')
|
266
|
+
|
267
|
+
puts errors.inspect
|
268
|
+
|
269
|
+
# [[:email, ["email must be filled"]], [:age, ["age must be greater than 18 (18 was given)"]]]
|
270
|
+
```
|
271
|
+
|
272
|
+
There are few major differences between how it works here and in `ActiveModel`:
|
273
|
+
|
274
|
+
* We have type checking as predicates, ie `gt?(18)` will not be applied if the value
|
275
|
+
is not an integer
|
276
|
+
* Thus, error messages are provided *only for the rules that failed*
|
277
|
+
* There's a planned feature for generating "validation hints" which lists information
|
278
|
+
about all possible rules
|
279
|
+
* Coercion is handled by `dry-data` coercible hash using its `form.*` types that
|
280
|
+
are dedicated for this type of coercions
|
281
|
+
* It's very easy to add your own types and coercions (more info/docs coming soon)
|
282
|
+
|
127
283
|
### Defining Custom Predicates
|
128
284
|
|
129
285
|
You can simply define predicate methods on your schema object:
|
@@ -158,25 +314,46 @@ class Schema < Dry::Validation::Schema
|
|
158
314
|
end
|
159
315
|
```
|
160
316
|
|
317
|
+
You need to provide error messages for your custom predicates if you want them
|
318
|
+
to work with `Schem#messages` interface.
|
319
|
+
|
320
|
+
You can learn how to do that in the [Error Messages](https://github.com/dryrb/dry-validation#error-messages) section.
|
321
|
+
|
161
322
|
## List of Built-In Predicates
|
162
323
|
|
163
|
-
|
324
|
+
### Basic
|
325
|
+
|
326
|
+
* `none?`
|
164
327
|
* `eql?`
|
165
|
-
* `
|
328
|
+
* `key?`
|
329
|
+
|
330
|
+
### Types
|
331
|
+
|
332
|
+
* `str?`
|
333
|
+
* `int?`
|
334
|
+
* `float?`
|
335
|
+
* `decimal?`
|
336
|
+
* `bool?`
|
337
|
+
* `date?`
|
338
|
+
* `date_time?`
|
339
|
+
* `time?`
|
340
|
+
* `array?`
|
341
|
+
* `hash?`
|
342
|
+
|
343
|
+
### Number, String, Collection
|
344
|
+
|
345
|
+
* `empty?`
|
166
346
|
* `filled?`
|
167
|
-
* `format?`
|
168
347
|
* `gt?`
|
169
348
|
* `gteq?`
|
170
|
-
* `inclusion?`
|
171
|
-
* `int?`
|
172
|
-
* `key?`
|
173
349
|
* `lt?`
|
174
350
|
* `lteq?`
|
175
351
|
* `max_size?`
|
176
352
|
* `min_size?`
|
177
|
-
* `nil?`
|
178
353
|
* `size?`
|
179
|
-
* `
|
354
|
+
* `format?`
|
355
|
+
* `inclusion?`
|
356
|
+
* `exclusion?`
|
180
357
|
|
181
358
|
## Error Messages
|
182
359
|
|
data/config/errors.yml
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
array?: "%{name} must be an array"
|
2
|
+
|
1
3
|
empty?: "%{name} cannot be empty"
|
2
4
|
|
3
5
|
exclusion?: "%{name} must not be one of: %{list}"
|
@@ -12,10 +14,24 @@ gt?: "%{name} must be greater than %{num} (%{value} was given)"
|
|
12
14
|
|
13
15
|
gteq?: "%{name} must be greater than or equal to %{num}"
|
14
16
|
|
17
|
+
hash?: "%{name} must be a hash"
|
18
|
+
|
15
19
|
inclusion?: "%{name} must be one of: %{list}"
|
16
20
|
|
21
|
+
bool?: "%{name} must be boolean"
|
22
|
+
|
17
23
|
int?: "%{name} must be an integer"
|
18
24
|
|
25
|
+
float?: "%{name} must be a float"
|
26
|
+
|
27
|
+
decimal?: "%{name} must be a decimal"
|
28
|
+
|
29
|
+
date?: "%{name} must be a date"
|
30
|
+
|
31
|
+
date_time?: "%{name} must be a date time"
|
32
|
+
|
33
|
+
time?: "%{name} must be a time"
|
34
|
+
|
19
35
|
key?: "%{name} is missing"
|
20
36
|
|
21
37
|
lt?: "%{name} must be less than %{num} (%{value} was given)"
|
data/dry-validation.gemspec
CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.add_runtime_dependency 'dry-configurable', '~> 0.1'
|
19
19
|
spec.add_runtime_dependency 'dry-container', '~> 0.2', '>= 0.2.6'
|
20
20
|
spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
21
|
+
spec.add_runtime_dependency 'dry-data', '~> 0.2', '>= 0.2.1'
|
21
22
|
|
22
23
|
spec.add_development_dependency 'bundler'
|
23
24
|
spec.add_development_dependency 'rake'
|
data/examples/each.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'dry-validation'
|
2
|
+
|
3
|
+
class Schema < Dry::Validation::Schema
|
4
|
+
key(:phone_numbers) do |phone_numbers|
|
5
|
+
phone_numbers.array? do
|
6
|
+
phone_numbers.each(&:str?)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
schema = Schema.new
|
12
|
+
|
13
|
+
errors = schema.messages(phone_numbers: '')
|
14
|
+
|
15
|
+
puts errors.inspect
|
16
|
+
|
17
|
+
errors = schema.messages(phone_numbers: ['123456789', 123456789])
|
18
|
+
|
19
|
+
puts errors.inspect
|
data/examples/form.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'dry-validation'
|
2
|
+
require 'dry/validation/schema/form'
|
3
|
+
|
4
|
+
class UserFormSchema < Dry::Validation::Schema::Form
|
5
|
+
key(:email) { |value| value.str? & value.filled? }
|
6
|
+
|
7
|
+
key(:age) { |value| value.int? & value.gt?(18) }
|
8
|
+
end
|
9
|
+
|
10
|
+
schema = UserFormSchema.new
|
11
|
+
|
12
|
+
errors = schema.messages('email' => '', 'age' => '18')
|
13
|
+
|
14
|
+
puts errors.inspect
|
15
|
+
# [[:email, ["email must be filled"]], [:age, ["age must be greater than 18 (18 was given)"]]]
|
data/examples/nested.rb
CHANGED
@@ -2,17 +2,19 @@ require 'dry-validation'
|
|
2
2
|
|
3
3
|
class Schema < Dry::Validation::Schema
|
4
4
|
key(:address) do |address|
|
5
|
-
address.
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
street
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
address.hash? do
|
6
|
+
address.key(:city) do |city|
|
7
|
+
city.min_size?(3)
|
8
|
+
end
|
9
|
+
|
10
|
+
address.key(:street) do |street|
|
11
|
+
street.filled?
|
12
|
+
end
|
13
|
+
|
14
|
+
address.key(:country) do |country|
|
15
|
+
country.key(:name, &:filled?)
|
16
|
+
country.key(:code, &:filled?)
|
17
|
+
end
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
data/lib/dry/validation.rb
CHANGED
@@ -42,10 +42,6 @@ module Dry
|
|
42
42
|
{ name: args[0][0] }
|
43
43
|
end
|
44
44
|
|
45
|
-
def visit_empty?(*args, value)
|
46
|
-
{ value: value }
|
47
|
-
end
|
48
|
-
|
49
45
|
def visit_exclusion?(*args, value)
|
50
46
|
{ list: args[0][0].join(', ') }
|
51
47
|
end
|
@@ -96,20 +92,8 @@ module Dry
|
|
96
92
|
end
|
97
93
|
end
|
98
94
|
|
99
|
-
def
|
100
|
-
{ value:
|
101
|
-
end
|
102
|
-
|
103
|
-
def visit_format?(*args, value)
|
104
|
-
{}
|
105
|
-
end
|
106
|
-
|
107
|
-
def visit_nil?(*args, value)
|
108
|
-
{}
|
109
|
-
end
|
110
|
-
|
111
|
-
def visit_filled?(*args)
|
112
|
-
{}
|
95
|
+
def method_missing(meth, *args)
|
96
|
+
{ value: args[1] }
|
113
97
|
end
|
114
98
|
end
|
115
99
|
end
|