dry-validation 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Join the chat at https://gitter.im/dryrb/chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dryrb/chat)
|
9
|
+
|
10
|
+
[![Gem Version](https://badge.fury.io/rb/dry-validation.svg)][gem]
|
11
|
+
[![Build Status](https://travis-ci.org/dryrb/dry-validation.svg?branch=master)][travis]
|
12
|
+
[![Dependency Status](https://gemnasium.com/dryrb/dry-validation.svg)][gemnasium]
|
13
|
+
[![Code Climate](https://codeclimate.com/github/dryrb/dry-validation/badges/gpa.svg)][codeclimate]
|
14
|
+
[![Test Coverage](https://codeclimate.com/github/dryrb/dry-validation/badges/coverage.svg)][codeclimate]
|
15
|
+
[![Inline docs](http://inch-ci.org/github/dryrb/dry-validation.svg?branch=master)][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
|