dry-validation 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -2
- data/Gemfile +4 -0
- data/README.md +131 -42
- data/config/errors.yml +36 -27
- data/examples/basic.rb +2 -4
- data/examples/each.rb +2 -2
- data/examples/form.rb +1 -2
- data/examples/nested.rb +2 -4
- data/examples/rule_ast.rb +0 -8
- data/lib/dry/validation.rb +0 -5
- data/lib/dry/validation/error.rb +2 -6
- data/lib/dry/validation/error_compiler.rb +19 -5
- data/lib/dry/validation/input_type_compiler.rb +2 -1
- data/lib/dry/validation/messages.rb +7 -58
- data/lib/dry/validation/messages/abstract.rb +75 -0
- data/lib/dry/validation/messages/i18n.rb +24 -0
- data/lib/dry/validation/messages/namespaced.rb +27 -0
- data/lib/dry/validation/messages/yaml.rb +50 -0
- data/lib/dry/validation/result.rb +19 -49
- data/lib/dry/validation/rule.rb +2 -2
- data/lib/dry/validation/rule/group.rb +21 -0
- data/lib/dry/validation/rule/result.rb +73 -0
- data/lib/dry/validation/rule_compiler.rb +5 -0
- data/lib/dry/validation/schema.rb +33 -14
- data/lib/dry/validation/schema/definition.rb +16 -0
- data/lib/dry/validation/schema/result.rb +21 -3
- data/lib/dry/validation/schema/rule.rb +1 -1
- data/lib/dry/validation/schema/value.rb +2 -1
- data/lib/dry/validation/version.rb +1 -1
- data/spec/fixtures/locales/en.yml +5 -0
- data/spec/fixtures/locales/pl.yml +14 -0
- data/spec/integration/custom_error_messages_spec.rb +4 -16
- data/spec/{unit → integration}/error_compiler_spec.rb +81 -39
- data/spec/integration/localized_error_messages_spec.rb +52 -0
- data/spec/integration/messages/i18n_spec.rb +71 -0
- data/spec/integration/rule_groups_spec.rb +35 -0
- data/spec/integration/schema_form_spec.rb +9 -9
- data/spec/integration/schema_spec.rb +2 -2
- data/spec/shared/predicates.rb +2 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/unit/rule/group_spec.rb +12 -0
- data/spec/unit/schema_spec.rb +35 -0
- metadata +24 -6
- data/spec/fixtures/errors.yml +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4c5757cf4c0d97231cb5077aa617a731ae8b77c
|
4
|
+
data.tar.gz: bb77ddf24b5108592e5ab92961bff80caefb20e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78c62e2a2585215397e12cb0621497e1b4332750b61501534b0e1f643f7d5ea7f82e05f3c64b9977a313acf87466c8031ec160e3706794b34ce90c1fe8a7300a
|
7
|
+
data.tar.gz: 1a9685102ac167001c910e617dee87cea45f6413d105f5231e93790bc3dad0c7199e67a0d838b294185520e26a5475ab56f9b13df08be92e5880a630b129ca20
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,34 @@
|
|
1
|
-
# v0.
|
1
|
+
# v0.3.0 to-be-released
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* I18n messages support (solnic)
|
6
|
+
* Ability to configure `messages` via `configure { config.messages = :i18n }` (solnic)
|
7
|
+
* `rule` interface in DSL for defining rules that depend on other rules (solnic)
|
8
|
+
* `confirmation` interface as a shortcut for defining "confirmation of" rule (solnic)
|
9
|
+
* Error messages can be now matched by input value type too (solnic)
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
|
13
|
+
* `optional` rule with coercions work correctly with `|` + multiple `&`s (solnic)
|
14
|
+
* `Schema#[]` checks registered predicates first before defaulting to its own predicates (solnic)
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
|
18
|
+
* `Schema#messages(input)` => `Schema#call(input).messages` (solnic)
|
19
|
+
* `Schema#call` returns `Schema::Result` which has access to all rule results,
|
20
|
+
errors and messages
|
21
|
+
* `Schema::Result#messages` returns a hash with rule names, messages and input values (solnic)
|
22
|
+
|
23
|
+
[Compare v0.2.0...HEAD](https://github.com/dryrb/dry-validation/compare/v0.2.0...HEAD)
|
24
|
+
|
25
|
+
# v0.2.0 2015-11-30
|
2
26
|
|
3
27
|
### Added
|
4
28
|
|
5
29
|
* `Schema::Form` with a built-in coercer inferred from type-check predicates (solnic)
|
6
30
|
* Ability to pass a block to predicate check in the DSL ie `value.hash? { ... }` (solnic)
|
7
|
-
* Optional keys using `
|
31
|
+
* Optional keys using `optional(:key_name) { ... }` interface in the DSL (solnic)
|
8
32
|
* New predicates:
|
9
33
|
- `bool?`
|
10
34
|
- `date?`
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -75,15 +75,15 @@ end
|
|
75
75
|
|
76
76
|
schema = Schema.new
|
77
77
|
|
78
|
-
errors = schema.
|
78
|
+
errors = schema.call(email: 'jane@doe.org', age: 19).messages
|
79
79
|
|
80
80
|
puts errors.inspect
|
81
81
|
# []
|
82
82
|
|
83
|
-
errors = schema.
|
83
|
+
errors = schema.call(email: nil, age: 19).messages
|
84
84
|
|
85
85
|
puts errors.inspect
|
86
|
-
#
|
86
|
+
# { :email => [["email must be filled", nil]] }
|
87
87
|
```
|
88
88
|
|
89
89
|
A couple of remarks:
|
@@ -112,15 +112,15 @@ end
|
|
112
112
|
|
113
113
|
schema = Schema.new
|
114
114
|
|
115
|
-
errors = schema.
|
115
|
+
errors = schema.call(email: 'jane@doe.org').messages
|
116
116
|
|
117
117
|
puts errors.inspect
|
118
118
|
# []
|
119
119
|
|
120
|
-
errors = schema.
|
120
|
+
errors = schema.call(email: 'jane@doe.org', age: 17).messages
|
121
121
|
|
122
122
|
puts errors.inspect
|
123
|
-
#
|
123
|
+
# { :age => [["age must be greater than 18"], 17] }
|
124
124
|
```
|
125
125
|
|
126
126
|
### Optional Values
|
@@ -140,20 +140,20 @@ end
|
|
140
140
|
|
141
141
|
schema = Schema.new
|
142
142
|
|
143
|
-
errors = schema.
|
143
|
+
errors = schema.call(email: 'jane@doe.org', age: nil).messages
|
144
144
|
|
145
145
|
puts errors.inspect
|
146
146
|
# []
|
147
147
|
|
148
|
-
errors = schema.
|
148
|
+
errors = schema.call(email: 'jane@doe.org', age: 19).messages
|
149
149
|
|
150
150
|
puts errors.inspect
|
151
151
|
# []
|
152
152
|
|
153
|
-
errors = schema.
|
153
|
+
errors = schema.call(email: 'jane@doe.org', age: 17).messages
|
154
154
|
|
155
155
|
puts errors.inspect
|
156
|
-
#
|
156
|
+
# { :age => [["age must be greater than 18"], 17] }
|
157
157
|
```
|
158
158
|
|
159
159
|
### Optional Key vs Value
|
@@ -201,15 +201,20 @@ end
|
|
201
201
|
|
202
202
|
schema = Schema.new
|
203
203
|
|
204
|
-
errors = schema.
|
204
|
+
errors = schema.call({}).messages
|
205
205
|
|
206
206
|
puts errors.inspect
|
207
|
-
#
|
207
|
+
# { :address => ["address is missing"] }
|
208
208
|
|
209
|
-
errors = schema.
|
209
|
+
errors = schema.call(address: { city: 'NYC' }).messages
|
210
210
|
|
211
|
-
puts errors.inspect
|
212
|
-
#
|
211
|
+
puts errors.to_h.inspect
|
212
|
+
# {
|
213
|
+
# :address => [
|
214
|
+
# { :street => ["street is missing"] },
|
215
|
+
# { :country => ["country is missing"] }
|
216
|
+
# ]
|
217
|
+
# }
|
213
218
|
```
|
214
219
|
|
215
220
|
### Array Elements
|
@@ -227,17 +232,50 @@ end
|
|
227
232
|
|
228
233
|
schema = Schema.new
|
229
234
|
|
230
|
-
errors = schema.
|
235
|
+
errors = schema.call(phone_numbers: '').messages
|
231
236
|
|
232
237
|
puts errors.inspect
|
233
|
-
#
|
238
|
+
# { :phone_numbers => [["phone_numbers must be an array", ""]] }
|
234
239
|
|
235
|
-
errors = schema.
|
240
|
+
errors = schema.call(phone_numbers: ['123456789', 123456789]).messages
|
236
241
|
|
237
242
|
puts errors.inspect
|
238
|
-
#
|
243
|
+
# {
|
244
|
+
# :phone_numbers => [
|
245
|
+
# {
|
246
|
+
# :phone_numbers => [
|
247
|
+
# ["phone_numbers must be a string", 123456789]
|
248
|
+
# ]
|
249
|
+
# }
|
250
|
+
# ]
|
251
|
+
# }
|
252
|
+
```
|
253
|
+
|
254
|
+
### Rules Depending On Other Rules
|
255
|
+
|
256
|
+
When a rule needs input from other rules and depends on their results you can
|
257
|
+
define it using `rule` DSL. A common example of this is "confirmation validation":
|
258
|
+
|
259
|
+
``` ruby
|
260
|
+
class Schema < Dry::Validation::Schema
|
261
|
+
key(:password, &:filled?)
|
262
|
+
key(:password_confirmation, &:filled?)
|
263
|
+
|
264
|
+
rule(:password_confirmation, eql?: [:password, :password_confirmation])
|
265
|
+
end
|
239
266
|
```
|
240
267
|
|
268
|
+
A short version of the same thing:
|
269
|
+
|
270
|
+
``` ruby
|
271
|
+
class Schema < Dry::Validation::Schema
|
272
|
+
confirmation(:password)
|
273
|
+
end
|
274
|
+
```
|
275
|
+
|
276
|
+
Notice that you must add `:password_confirmation` error message configuration if
|
277
|
+
you want to have the error converted to a message.
|
278
|
+
|
241
279
|
### Form Validation With Coercions
|
242
280
|
|
243
281
|
Probably the most common use case is to validate form params. This is a special
|
@@ -262,11 +300,13 @@ end
|
|
262
300
|
|
263
301
|
schema = UserFormSchema.new
|
264
302
|
|
265
|
-
errors = schema.
|
303
|
+
errors = schema.call('email' => '', 'age' => '18').messages
|
266
304
|
|
267
305
|
puts errors.inspect
|
268
|
-
|
269
|
-
#
|
306
|
+
# {
|
307
|
+
# :email => [["email must be filled", nil]],
|
308
|
+
# :age => [["age must be greater than 18 (18 was given)", 18]]
|
309
|
+
# }
|
270
310
|
```
|
271
311
|
|
272
312
|
There are few major differences between how it works here and in `ActiveModel`:
|
@@ -350,7 +390,8 @@ You can learn how to do that in the [Error Messages](https://github.com/dryrb/dr
|
|
350
390
|
* `lteq?`
|
351
391
|
* `max_size?`
|
352
392
|
* `min_size?`
|
353
|
-
* `size
|
393
|
+
* `size?(int)`
|
394
|
+
* `size?(range)`
|
354
395
|
* `format?`
|
355
396
|
* `inclusion?`
|
356
397
|
* `exclusion?`
|
@@ -359,7 +400,7 @@ You can learn how to do that in the [Error Messages](https://github.com/dryrb/dr
|
|
359
400
|
|
360
401
|
By default `dry-validation` comes with a set of pre-defined error messages for
|
361
402
|
every built-in predicate. They are defined in [a yaml file](https://github.com/dryrb/dry-validation/blob/master/config/errors.yml)
|
362
|
-
which is shipped with the gem.
|
403
|
+
which is shipped with the gem. This file is compatible with `I18n` format.
|
363
404
|
|
364
405
|
You can provide your own messages and configure your schemas to use it like that:
|
365
406
|
|
@@ -380,18 +421,31 @@ end
|
|
380
421
|
Lookup rules:
|
381
422
|
|
382
423
|
``` yaml
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
424
|
+
en:
|
425
|
+
errors:
|
426
|
+
size?:
|
427
|
+
arg:
|
428
|
+
default: "%{name} size must be %{num}"
|
429
|
+
range: "%{name} size must be within %{left} - %{right}"
|
430
|
+
|
431
|
+
value:
|
432
|
+
string:
|
433
|
+
arg:
|
434
|
+
default: "%{name} length must be %{num}"
|
435
|
+
range: "%{name} length must be within %{left} - %{right}"
|
436
|
+
|
437
|
+
filled?: "%{name} must be filled"
|
438
|
+
|
439
|
+
rules:
|
440
|
+
email:
|
441
|
+
filled?: "the email is missing"
|
442
|
+
|
443
|
+
user:
|
444
|
+
filled?: "%{name} name cannot be blank"
|
445
|
+
|
446
|
+
rules:
|
447
|
+
address:
|
448
|
+
filled?: "You gotta tell us where you live"
|
395
449
|
```
|
396
450
|
|
397
451
|
Given the yaml file above, messages lookup works as follows:
|
@@ -399,15 +453,25 @@ Given the yaml file above, messages lookup works as follows:
|
|
399
453
|
``` ruby
|
400
454
|
messages = Dry::Validation::Messages.load('/path/to/our/errors.yml')
|
401
455
|
|
402
|
-
|
403
|
-
messages
|
404
|
-
messages
|
456
|
+
# matching arg type for size? predicate
|
457
|
+
messages[:size?, rule: :name, arg_type: Fixnum] # => "%{name} size must be %{num}"
|
458
|
+
messages[:size?, rule: :name, arg_type: Range] # => "%{name} size must within %{left} - %{right}"
|
459
|
+
|
460
|
+
# matching val type for size? predicate
|
461
|
+
messages[:size?, rule: :name, val_type: String] # => "%{name} length must be %{num}"
|
462
|
+
|
463
|
+
# matching predicate
|
464
|
+
messages[:filled?, rule: :age] # => "%{name} must be filled"
|
465
|
+
messages[:filled?, rule: :address] # => "%{name} must be filled"
|
466
|
+
|
467
|
+
# matching predicate for a specific rule
|
468
|
+
messages[:filled?, rule: :email] # => "the email is missing"
|
405
469
|
|
406
470
|
# with namespaced messages
|
407
471
|
user_messages = messages.namespaced(:user)
|
408
472
|
|
409
|
-
user_messages
|
410
|
-
user_messages
|
473
|
+
user_messages[:filled?, rule: :age] # "%{name} cannot be blank"
|
474
|
+
user_messages[:filled?, rule: :address] # "You gotta tell us where you live"
|
411
475
|
```
|
412
476
|
|
413
477
|
By configuring `messages_file` and/or `namespace` in a schema, default messages
|
@@ -415,7 +479,32 @@ are going to be automatically merged with your overrides and/or namespaced.
|
|
415
479
|
|
416
480
|
## I18n Integration
|
417
481
|
|
418
|
-
|
482
|
+
If you are using `i18n` gem and load it before `dry-validation` then you'll be
|
483
|
+
able to configure a schema to use `i18n` messages:
|
484
|
+
|
485
|
+
``` ruby
|
486
|
+
require 'i18n'
|
487
|
+
require 'dry-validation'
|
488
|
+
|
489
|
+
class Schema < Dry::Validation::Schema
|
490
|
+
configure { config.messages = :i18n }
|
491
|
+
|
492
|
+
key(:email, &:filled?)
|
493
|
+
end
|
494
|
+
|
495
|
+
schema = Schema.new
|
496
|
+
|
497
|
+
# return default translations
|
498
|
+
puts schema.call(email: '').messages
|
499
|
+
{ :email => ["email must be filled"] }
|
500
|
+
|
501
|
+
# return other translations (assuming you have it :))
|
502
|
+
puts schema.call(email: '').messages(locale: :pl)
|
503
|
+
{ :email => ["email musi być wypełniony"] }
|
504
|
+
```
|
505
|
+
|
506
|
+
Important: I18n must be initialized before using schema, `dry-validation` does
|
507
|
+
not try to do it for you, it only sets its default error translations automatically.
|
419
508
|
|
420
509
|
## Rule AST
|
421
510
|
|
data/config/errors.yml
CHANGED
@@ -1,51 +1,60 @@
|
|
1
|
-
|
1
|
+
en:
|
2
|
+
errors:
|
3
|
+
array?: "%{name} must be an array"
|
2
4
|
|
3
|
-
empty?: "%{name} cannot be empty"
|
5
|
+
empty?: "%{name} cannot be empty"
|
4
6
|
|
5
|
-
exclusion?: "%{name} must not be one of: %{list}"
|
7
|
+
exclusion?: "%{name} must not be one of: %{list}"
|
6
8
|
|
7
|
-
eql?: "%{name} must be equal to %{eql_value}"
|
9
|
+
eql?: "%{name} must be equal to %{eql_value}"
|
8
10
|
|
9
|
-
filled?: "%{name} must be filled"
|
11
|
+
filled?: "%{name} must be filled"
|
10
12
|
|
11
|
-
format?: "%{name} is in invalid format"
|
13
|
+
format?: "%{name} is in invalid format"
|
12
14
|
|
13
|
-
gt?: "%{name} must be greater than %{num}
|
15
|
+
gt?: "%{name} must be greater than %{num}"
|
14
16
|
|
15
|
-
gteq?: "%{name} must be greater than or equal to %{num}"
|
17
|
+
gteq?: "%{name} must be greater than or equal to %{num}"
|
16
18
|
|
17
|
-
hash?: "%{name} must be a hash"
|
19
|
+
hash?: "%{name} must be a hash"
|
18
20
|
|
19
|
-
inclusion?: "%{name} must be one of: %{list}"
|
21
|
+
inclusion?: "%{name} must be one of: %{list}"
|
20
22
|
|
21
|
-
bool?: "%{name} must be boolean"
|
23
|
+
bool?: "%{name} must be boolean"
|
22
24
|
|
23
|
-
int?: "%{name} must be an integer"
|
25
|
+
int?: "%{name} must be an integer"
|
24
26
|
|
25
|
-
float?: "%{name} must be a float"
|
27
|
+
float?: "%{name} must be a float"
|
26
28
|
|
27
|
-
decimal?: "%{name} must be a decimal"
|
29
|
+
decimal?: "%{name} must be a decimal"
|
28
30
|
|
29
|
-
date?: "%{name} must be a date"
|
31
|
+
date?: "%{name} must be a date"
|
30
32
|
|
31
|
-
date_time?: "%{name} must be a date time"
|
33
|
+
date_time?: "%{name} must be a date time"
|
32
34
|
|
33
|
-
time?: "%{name} must be a time"
|
35
|
+
time?: "%{name} must be a time"
|
34
36
|
|
35
|
-
key?: "%{name} is missing"
|
37
|
+
key?: "%{name} is missing"
|
36
38
|
|
37
|
-
lt?: "%{name} must be less than %{num} (%{value} was given)"
|
39
|
+
lt?: "%{name} must be less than %{num} (%{value} was given)"
|
38
40
|
|
39
|
-
lteq?: "%{name} must be less than or equal to %{num}"
|
41
|
+
lteq?: "%{name} must be less than or equal to %{num}"
|
40
42
|
|
41
|
-
max_size?: "%{name} size cannot be greater than %{num}"
|
43
|
+
max_size?: "%{name} size cannot be greater than %{num}"
|
42
44
|
|
43
|
-
min_size?: "%{name} size cannot be less than %{num}"
|
45
|
+
min_size?: "%{name} size cannot be less than %{num}"
|
44
46
|
|
45
|
-
|
47
|
+
none?: "%{name} cannot be defined"
|
46
48
|
|
47
|
-
|
48
|
-
range: "%{name} size must be within %{left} - %{right}"
|
49
|
-
default: "%{name} size must be %{num}"
|
49
|
+
str?: "%{name} must be a string"
|
50
50
|
|
51
|
-
|
51
|
+
size?:
|
52
|
+
arg:
|
53
|
+
default: "%{name} size must be %{num}"
|
54
|
+
range: "%{name} size must be within %{left} - %{right}"
|
55
|
+
|
56
|
+
value:
|
57
|
+
string:
|
58
|
+
arg:
|
59
|
+
default: "%{name} length must be %{num}"
|
60
|
+
range: "%{name} length must be within %{left} - %{right}"
|
data/examples/basic.rb
CHANGED
@@ -10,12 +10,10 @@ end
|
|
10
10
|
|
11
11
|
schema = Schema.new
|
12
12
|
|
13
|
-
errors = schema.
|
13
|
+
errors = schema.call(email: 'jane@doe.org', age: 19).messages
|
14
14
|
|
15
15
|
puts errors.inspect
|
16
|
-
# []
|
17
16
|
|
18
|
-
errors = schema.
|
17
|
+
errors = schema.call(email: nil, age: 19).messages
|
19
18
|
|
20
19
|
puts errors.inspect
|
21
|
-
# [[:email, ["email must be filled"]]]
|
data/examples/each.rb
CHANGED
@@ -10,10 +10,10 @@ end
|
|
10
10
|
|
11
11
|
schema = Schema.new
|
12
12
|
|
13
|
-
errors = schema.
|
13
|
+
errors = schema.call(phone_numbers: '').messages
|
14
14
|
|
15
15
|
puts errors.inspect
|
16
16
|
|
17
|
-
errors = schema.
|
17
|
+
errors = schema.call(phone_numbers: ['123456789', 123456789]).messages
|
18
18
|
|
19
19
|
puts errors.inspect
|