dry-validation 0.1.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 +7 -0
- data/.gitignore +8 -0
- data/.rspec +3 -0
- data/.rubocop.yml +16 -0
- data/.rubocop_todo.yml +7 -0
- data/.travis.yml +29 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +11 -0
- data/LICENSE +20 -0
- data/README.md +297 -0
- data/Rakefile +12 -0
- data/config/errors.yml +35 -0
- data/dry-validation.gemspec +25 -0
- data/examples/basic.rb +21 -0
- data/examples/nested.rb +30 -0
- data/examples/rule_ast.rb +33 -0
- data/lib/dry-validation.rb +1 -0
- data/lib/dry/validation.rb +12 -0
- data/lib/dry/validation/error.rb +43 -0
- data/lib/dry/validation/error_compiler.rb +116 -0
- data/lib/dry/validation/messages.rb +71 -0
- data/lib/dry/validation/predicate.rb +39 -0
- data/lib/dry/validation/predicate_set.rb +22 -0
- data/lib/dry/validation/predicates.rb +88 -0
- data/lib/dry/validation/result.rb +64 -0
- data/lib/dry/validation/rule.rb +125 -0
- data/lib/dry/validation/rule_compiler.rb +57 -0
- data/lib/dry/validation/schema.rb +74 -0
- data/lib/dry/validation/schema/definition.rb +15 -0
- data/lib/dry/validation/schema/key.rb +39 -0
- data/lib/dry/validation/schema/rule.rb +28 -0
- data/lib/dry/validation/schema/value.rb +31 -0
- data/lib/dry/validation/version.rb +5 -0
- data/rakelib/rubocop.rake +18 -0
- data/spec/fixtures/errors.yml +4 -0
- data/spec/integration/custom_error_messages_spec.rb +35 -0
- data/spec/integration/custom_predicates_spec.rb +57 -0
- data/spec/integration/validation_spec.rb +118 -0
- data/spec/shared/predicates.rb +31 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/unit/error_compiler_spec.rb +165 -0
- data/spec/unit/predicate_spec.rb +37 -0
- data/spec/unit/predicates/empty_spec.rb +38 -0
- data/spec/unit/predicates/eql_spec.rb +21 -0
- data/spec/unit/predicates/exclusion_spec.rb +35 -0
- data/spec/unit/predicates/filled_spec.rb +38 -0
- data/spec/unit/predicates/format_spec.rb +21 -0
- data/spec/unit/predicates/gt_spec.rb +40 -0
- data/spec/unit/predicates/gteq_spec.rb +40 -0
- data/spec/unit/predicates/inclusion_spec.rb +35 -0
- data/spec/unit/predicates/int_spec.rb +34 -0
- data/spec/unit/predicates/key_spec.rb +29 -0
- data/spec/unit/predicates/lt_spec.rb +40 -0
- data/spec/unit/predicates/lteq_spec.rb +40 -0
- data/spec/unit/predicates/max_size_spec.rb +49 -0
- data/spec/unit/predicates/min_size_spec.rb +49 -0
- data/spec/unit/predicates/nil_spec.rb +28 -0
- data/spec/unit/predicates/size_spec.rb +49 -0
- data/spec/unit/predicates/str_spec.rb +32 -0
- data/spec/unit/rule/each_spec.rb +20 -0
- data/spec/unit/rule/key_spec.rb +27 -0
- data/spec/unit/rule/set_spec.rb +32 -0
- data/spec/unit/rule/value_spec.rb +42 -0
- data/spec/unit/rule_compiler_spec.rb +86 -0
- metadata +230 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6acd14c8b93a29eed518ef8934921cf35d0faf17
|
4
|
+
data.tar.gz: 83c9f549fb92f5ff4cf3f5c95ec8029e81c045d2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b7411edfe3d00dbc645d9c48a7df9dd952bf36c01e59f1a55e39114e0a757afd6b4e66b57a07380b658a146b2757651f4a1b47d61f29565d464d2b7896cdb04f
|
7
|
+
data.tar.gz: 50201382e0ed26bcebcf19f1d9dafe2c7c3ee260c9654afdb3b6002ce7cbf4bc83ad549e6ffff9f6636ca1fb9aa6eaadea1371044a08e33425443768eef2e512
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Generated by `rubocop --auto-gen-config`
|
2
|
+
inherit_from: .rubocop_todo.yml
|
3
|
+
|
4
|
+
Metrics/LineLength:
|
5
|
+
Max: 100
|
6
|
+
|
7
|
+
Style/Documentation:
|
8
|
+
Enabled: false
|
9
|
+
|
10
|
+
Lint/HandleExceptions:
|
11
|
+
Exclude:
|
12
|
+
- rakelib/*.rake
|
13
|
+
|
14
|
+
Style/FileName:
|
15
|
+
Exclude:
|
16
|
+
- lib/dry-validation.rb
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2015-10-30 01:32:46 +0000 using RuboCop version 0.34.2.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
data/.travis.yml
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
language: ruby
|
2
|
+
sudo: false
|
3
|
+
cache: bundler
|
4
|
+
bundler_args: --without console
|
5
|
+
script:
|
6
|
+
- bundle exec rake spec
|
7
|
+
rvm:
|
8
|
+
- 2.0
|
9
|
+
- 2.1
|
10
|
+
- 2.2
|
11
|
+
- rbx-2
|
12
|
+
- jruby-9000
|
13
|
+
- ruby-head
|
14
|
+
- jruby-head
|
15
|
+
env:
|
16
|
+
global:
|
17
|
+
- JRUBY_OPTS='--dev -J-Xmx1024M'
|
18
|
+
matrix:
|
19
|
+
allow_failures:
|
20
|
+
- rvm: ruby-head
|
21
|
+
- rvm: jruby-head
|
22
|
+
notifications:
|
23
|
+
email: false
|
24
|
+
webhooks:
|
25
|
+
urls:
|
26
|
+
- https://webhooks.gitter.im/e/19098b4253a72c9796db
|
27
|
+
on_success: change # options: [always|never|change] default: always
|
28
|
+
on_failure: always # options: [always|never|change] default: always
|
29
|
+
on_start: false # default: false
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Ruby Object Mapper
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,297 @@
|
|
1
|
+
# dry-validation <a href="https://gitter.im/dryrb/chat" target="_blank"></a>
|
2
|
+
|
3
|
+
<a href="https://rubygems.org/gems/dry-validation" target="_blank"></a>
|
4
|
+
<a href="https://travis-ci.org/dryrb/dry-validation" target="_blank"></a>
|
5
|
+
<a href="https://gemnasium.com/dryrb/dry-validation" target="_blank"></a>
|
6
|
+
<a href="https://codeclimate.com/github/dryrb/dry-validation" target="_blank"></a>
|
7
|
+
<a href="http://inch-ci.org/github/dryrb/dry-validation" target="_blank"></a>
|
8
|
+
|
9
|
+
Data validation library based on predicate logic and rule composition.
|
10
|
+
|
11
|
+
## Overview
|
12
|
+
|
13
|
+
Unlike other, well known, validation solutions in Ruby, `dry-validation` takes
|
14
|
+
a different approach and focuses a lot on explicitness, clarity and preciseness
|
15
|
+
of validation logic. It is designed to work with any data input, whether it's a
|
16
|
+
simple hash, an array or a complex object with deeply nested data.
|
17
|
+
|
18
|
+
It is based on a simple idea that each validation is encapsulated by a simple,
|
19
|
+
stateless predicate, that receives some input and returns either `true` or `false`.
|
20
|
+
|
21
|
+
Those predicates are encapsulated by `rules` which can be composed together using
|
22
|
+
`predicate logic`. This means you can use the common logic operators to build up
|
23
|
+
a validation `schema`.
|
24
|
+
|
25
|
+
It's very explicit, powerful and extendible.
|
26
|
+
|
27
|
+
Validations can be described with great precision, `dry-validation` eliminates
|
28
|
+
ambigious concepts like `presence` validation where we can't really say whether
|
29
|
+
some attribute or key is *missing* or it's just that the value is `nil`.
|
30
|
+
|
31
|
+
There's also the concept of type-safety, completely missing in other validation
|
32
|
+
libraries, which is quite important and useful. It means you can compose a validation
|
33
|
+
that does rely on the type of a given value. In example it makes no sense to validate
|
34
|
+
each element of an array when it turns out to be an empty string.
|
35
|
+
|
36
|
+
## The DSL
|
37
|
+
|
38
|
+
The core of `dry-validation` is rules composition and predicate logic. The DSL
|
39
|
+
is a simple front-end for that. It only allows you to define the rules by using
|
40
|
+
predicate identifiers. There are no magical options, conditionals and custom
|
41
|
+
validation blocks known from other libraries. The focus is on pure validation
|
42
|
+
logic.
|
43
|
+
|
44
|
+
## Examples
|
45
|
+
|
46
|
+
### Basic
|
47
|
+
|
48
|
+
Here's a basic example where we validate following things:
|
49
|
+
|
50
|
+
* The input *must have a key* called `:email`
|
51
|
+
* Provided the email key is present, its value *must be filled*
|
52
|
+
* The input *must have a key* called `:age`
|
53
|
+
* Provided the age key is present, its value *must be an integer* and it *must be greater than 18*
|
54
|
+
|
55
|
+
This can be easily expressed through the DSL:
|
56
|
+
|
57
|
+
``` ruby
|
58
|
+
require 'dry-validation'
|
59
|
+
|
60
|
+
class Schema < Dry::Validation::Schema
|
61
|
+
key(:email) { |email| email.filled? }
|
62
|
+
|
63
|
+
key(:age) do |age|
|
64
|
+
age.int? & age.gt?(18)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
schema = Schema.new
|
69
|
+
|
70
|
+
errors = schema.messages(email: 'jane@doe.org', age: 19)
|
71
|
+
|
72
|
+
puts errors.inspect
|
73
|
+
# []
|
74
|
+
|
75
|
+
errors = schema.messages(email: nil, age: 19)
|
76
|
+
|
77
|
+
puts errors.inspect
|
78
|
+
# [[:email, ["email must be filled"]]]
|
79
|
+
```
|
80
|
+
|
81
|
+
A couple of remarks:
|
82
|
+
|
83
|
+
* `key` assumes that we want to use the `:key?` predicate to check the existance of that key
|
84
|
+
* `age.gt?(18)` translates to calling a predicate like this: `schema[:gt?].(18, age)`
|
85
|
+
* `age.int? & age.gt?(18)` is a conjunction, so we don't bother about `gt?` unless `int?` returns `true`
|
86
|
+
* You can also use `|` for disjunction
|
87
|
+
* Schema object does not carry the input as its state, nor does it know how to access the input values, we
|
88
|
+
pass the input to `call` and get error set as the response
|
89
|
+
|
90
|
+
### Nested Hash
|
91
|
+
|
92
|
+
We are free to define validations for anything, including deeply nested structures:
|
93
|
+
|
94
|
+
``` ruby
|
95
|
+
require 'dry-validation'
|
96
|
+
|
97
|
+
class Schema < Dry::Validation::Schema
|
98
|
+
key(:address) do |address|
|
99
|
+
address.key(:city) do |city|
|
100
|
+
city.min_size?(3)
|
101
|
+
end
|
102
|
+
|
103
|
+
address.key(:street) do |street|
|
104
|
+
street.filled?
|
105
|
+
end
|
106
|
+
|
107
|
+
address.key(:country) do |country|
|
108
|
+
country.key(:name, &:filled?)
|
109
|
+
country.key(:code, &:filled?)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
schema = Schema.new
|
115
|
+
|
116
|
+
errors = schema.messages({})
|
117
|
+
|
118
|
+
puts errors.inspect
|
119
|
+
# [[:address, ["address is missing"]]]
|
120
|
+
|
121
|
+
errors = schema.messages(address: { city: 'NYC' })
|
122
|
+
|
123
|
+
puts errors.inspect
|
124
|
+
# [[:address, [[:street, ["street is missing"]], [:country, ["country is missing"]]]]]
|
125
|
+
```
|
126
|
+
|
127
|
+
### Defining Custom Predicates
|
128
|
+
|
129
|
+
You can simply define predicate methods on your schema object:
|
130
|
+
|
131
|
+
``` ruby
|
132
|
+
class Schema < Dry::Validation::Schema
|
133
|
+
key(:email) { |value| value.str? & value.email? }
|
134
|
+
|
135
|
+
def email?(value)
|
136
|
+
! /magical-regex-that-matches-emails/.match(value).nil?
|
137
|
+
end
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
You can also re-use a predicate container across multiple schemas:
|
142
|
+
|
143
|
+
``` ruby
|
144
|
+
module MyPredicates
|
145
|
+
include Dry::Validation::Predicates
|
146
|
+
|
147
|
+
predicate(:email?) do |input|
|
148
|
+
! /magical-regex-that-matches-emails/.match(value).nil?
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class Schema < Dry::Validation::Schema
|
153
|
+
configure do |config|
|
154
|
+
config.predicates = MyPredicates
|
155
|
+
end
|
156
|
+
|
157
|
+
key(:email) { |value| value.str? & value.email? }
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
## List of Built-In Predicates
|
162
|
+
|
163
|
+
* `empty?`
|
164
|
+
* `eql?`
|
165
|
+
* `exclusion?`
|
166
|
+
* `filled?`
|
167
|
+
* `format?`
|
168
|
+
* `gt?`
|
169
|
+
* `gteq?`
|
170
|
+
* `inclusion?`
|
171
|
+
* `int?`
|
172
|
+
* `key?`
|
173
|
+
* `lt?`
|
174
|
+
* `lteq?`
|
175
|
+
* `max_size?`
|
176
|
+
* `min_size?`
|
177
|
+
* `nil?`
|
178
|
+
* `size?`
|
179
|
+
* `str?`
|
180
|
+
|
181
|
+
## Error Messages
|
182
|
+
|
183
|
+
By default `dry-validation` comes with a set of pre-defined error messages for
|
184
|
+
every built-in predicate. They are defined in [a yaml file](https://github.com/dryrb/dry-validation/blob/master/config/errors.yml)
|
185
|
+
which is shipped with the gem.
|
186
|
+
|
187
|
+
You can provide your own messages and configure your schemas to use it like that:
|
188
|
+
|
189
|
+
``` ruby
|
190
|
+
class Schema < Dry::Validation::Schema
|
191
|
+
configure { |config| config.messages_file = '/path/to/my/errors.yml' }
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
You can also provide a namespace per-schema that will be used by default:
|
196
|
+
|
197
|
+
``` ruby
|
198
|
+
class Schema < Dry::Validation::Schema
|
199
|
+
configure { |config| config.namespace = :user }
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
Lookup rules:
|
204
|
+
|
205
|
+
``` yaml
|
206
|
+
filled?: "%{name} must be filled"
|
207
|
+
|
208
|
+
attributes:
|
209
|
+
email:
|
210
|
+
filled?: "the email is missing"
|
211
|
+
|
212
|
+
user:
|
213
|
+
filled?: "%{name} name cannot be blank"
|
214
|
+
|
215
|
+
attributes:
|
216
|
+
address:
|
217
|
+
filled?: "You gotta tell us where you live"
|
218
|
+
```
|
219
|
+
|
220
|
+
Given the yaml file above, messages lookup works as follows:
|
221
|
+
|
222
|
+
``` ruby
|
223
|
+
messages = Dry::Validation::Messages.load('/path/to/our/errors.yml')
|
224
|
+
|
225
|
+
messages.lookup(:filled?, :age) # => "age must be filled"
|
226
|
+
messages.lookup(:filled?, :address) # => "address must be filled"
|
227
|
+
messages.lookup(:filled?, :email) # => "the email is missing"
|
228
|
+
|
229
|
+
# with namespaced messages
|
230
|
+
user_messages = messages.namespaced(:user)
|
231
|
+
|
232
|
+
user_messages.lookup(:filled?, :age) # "age cannot be blank"
|
233
|
+
user_messages.lookup(:filled?, :address) # "You gotta tell us where you live"
|
234
|
+
```
|
235
|
+
|
236
|
+
By configuring `messages_file` and/or `namespace` in a schema, default messages
|
237
|
+
are going to be automatically merged with your overrides and/or namespaced.
|
238
|
+
|
239
|
+
## I18n Integration
|
240
|
+
|
241
|
+
Coming (very) soon...
|
242
|
+
|
243
|
+
## Rule AST
|
244
|
+
|
245
|
+
Internally, `dry-validation` uses a simple AST representation of rules and errors
|
246
|
+
to produce rule objects and error messages. If you would like to programatically
|
247
|
+
generate rules, it is a very simple process:
|
248
|
+
|
249
|
+
``` ruby
|
250
|
+
ast = [
|
251
|
+
[
|
252
|
+
:and,
|
253
|
+
[
|
254
|
+
[:key, [:age, [:predicate, [:key?, []]]]],
|
255
|
+
[
|
256
|
+
:and,
|
257
|
+
[
|
258
|
+
[:val, [:age, [:predicate, [:filled?, []]]]],
|
259
|
+
[:val, [:age, [:predicate, [:gt?, [18]]]]]
|
260
|
+
]
|
261
|
+
]
|
262
|
+
]
|
263
|
+
]
|
264
|
+
]
|
265
|
+
|
266
|
+
compiler = Dry::Validation::RuleCompiler.new(Dry::Validation::Predicates)
|
267
|
+
|
268
|
+
# compile an array of rule objects
|
269
|
+
rules = compiler.call(ast)
|
270
|
+
|
271
|
+
puts rules.inspect
|
272
|
+
# [
|
273
|
+
# #<Dry::Validation::Rule::Conjunction
|
274
|
+
# left=#<Dry::Validation::Rule::Key name=:age predicate=#<Dry::Validation::Predicate id=:key?>>
|
275
|
+
# right=#<Dry::Validation::Rule::Conjunction
|
276
|
+
# left=#<Dry::Validation::Rule::Value name=:age predicate=#<Dry::Validation::Predicate id=:filled?>>
|
277
|
+
# right=#<Dry::Validation::Rule::Value name=:age predicate=#<Dry::Validation::Predicate id=:gt?>>>>
|
278
|
+
# ]
|
279
|
+
|
280
|
+
# dump it back to ast
|
281
|
+
puts rules.map(&:to_ary).inspect
|
282
|
+
# [[:and, [:key, [:age, [:predicate, [:key?, [:age]]]]], [[:and, [:val, [:age, [:predicate, [:filled?, []]]]], [[:val, [:age, [:predicate, [:gt?, [18]]]]]]]]]]
|
283
|
+
```
|
284
|
+
|
285
|
+
Complete docs for the AST format are coming soon, for now please refer to
|
286
|
+
[this spec](https://github.com/dryrb/dry-validation/blob/master/spec/unit/rule_compiler_spec.rb).
|
287
|
+
|
288
|
+
## Status and Roadmap
|
289
|
+
|
290
|
+
This library is in a very early stage of development but you are encauraged to
|
291
|
+
try it out and provide feedback.
|
292
|
+
|
293
|
+
For planned features check out [the issues](https://github.com/dryrb/dry-validation/labels/feature).
|
294
|
+
|
295
|
+
## License
|
296
|
+
|
297
|
+
See `LICENSE` file.
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
5
|
+
|
6
|
+
require 'rspec/core'
|
7
|
+
require 'rspec/core/rake_task'
|
8
|
+
|
9
|
+
task default: :spec
|
10
|
+
|
11
|
+
desc 'Run all specs in spec directory'
|
12
|
+
RSpec::Core::RakeTask.new(:spec)
|
data/config/errors.yml
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
empty?: "%{name} cannot be empty"
|
2
|
+
|
3
|
+
exclusion?: "%{name} must not be one of: %{list}"
|
4
|
+
|
5
|
+
eql?: "%{name} must be equal to %{eql_value}"
|
6
|
+
|
7
|
+
filled?: "%{name} must be filled"
|
8
|
+
|
9
|
+
format?: "%{name} is in invalid format"
|
10
|
+
|
11
|
+
gt?: "%{name} must be greater than %{num} (%{value} was given)"
|
12
|
+
|
13
|
+
gteq?: "%{name} must be greater than or equal to %{num}"
|
14
|
+
|
15
|
+
inclusion?: "%{name} must be one of: %{list}"
|
16
|
+
|
17
|
+
int?: "%{name} must be an integer"
|
18
|
+
|
19
|
+
key?: "%{name} is missing"
|
20
|
+
|
21
|
+
lt?: "%{name} must be less than %{num} (%{value} was given)"
|
22
|
+
|
23
|
+
lteq?: "%{name} must be less than or equal to %{num}"
|
24
|
+
|
25
|
+
max_size?: "%{name} size cannot be greater than %{num}"
|
26
|
+
|
27
|
+
min_size?: "%{name} size cannot be less than %{num}"
|
28
|
+
|
29
|
+
nil?: "%{name} cannot be nil"
|
30
|
+
|
31
|
+
size?:
|
32
|
+
range: "%{name} size must be within %{left} - %{right}"
|
33
|
+
default: "%{name} size must be %{num}"
|
34
|
+
|
35
|
+
str?: "%{name} must be a string"
|