dry-validation 0.8.0 → 0.9.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 -1
- data/CHANGELOG.md +39 -1
- data/benchmarks/benchmark_schema_invalid_huge.rb +52 -0
- data/benchmarks/profile_schema_huge_invalid.rb +30 -0
- data/config/errors.yml +3 -2
- data/dry-validation.gemspec +2 -2
- data/lib/dry/validation.rb +20 -32
- data/lib/dry/validation/constants.rb +6 -0
- data/lib/dry/validation/error.rb +5 -2
- data/lib/dry/validation/error_compiler.rb +46 -116
- data/lib/dry/validation/executor.rb +105 -0
- data/lib/dry/validation/hint_compiler.rb +36 -68
- data/lib/dry/validation/message.rb +86 -0
- data/lib/dry/validation/message_compiler.rb +141 -0
- data/lib/dry/validation/message_set.rb +70 -0
- data/lib/dry/validation/messages/abstract.rb +1 -1
- data/lib/dry/validation/messages/i18n.rb +5 -0
- data/lib/dry/validation/predicate_registry.rb +8 -3
- data/lib/dry/validation/result.rb +6 -7
- data/lib/dry/validation/schema.rb +21 -227
- data/lib/dry/validation/schema/check.rb +1 -1
- data/lib/dry/validation/schema/class_interface.rb +193 -0
- data/lib/dry/validation/schema/deprecated.rb +1 -2
- data/lib/dry/validation/schema/key.rb +4 -0
- data/lib/dry/validation/schema/value.rb +12 -7
- data/lib/dry/validation/schema_compiler.rb +20 -1
- data/lib/dry/validation/type_specs.rb +70 -0
- data/lib/dry/validation/version.rb +1 -1
- data/spec/fixtures/locales/pl.yml +1 -1
- data/spec/integration/custom_predicates_spec.rb +37 -0
- data/spec/integration/error_compiler_spec.rb +39 -39
- data/spec/integration/form/predicates/key_spec.rb +10 -18
- data/spec/integration/form/predicates/size/fixed_spec.rb +8 -12
- data/spec/integration/form/predicates/size/range_spec.rb +7 -7
- data/spec/integration/hints_spec.rb +17 -0
- data/spec/integration/messages/i18n_spec.rb +2 -2
- data/spec/integration/schema/check_rules_spec.rb +2 -2
- data/spec/integration/schema/defining_base_schema_spec.rb +38 -0
- data/spec/integration/schema/dynamic_predicate_args_spec.rb +18 -0
- data/spec/integration/schema/macros/each_spec.rb +2 -2
- data/spec/integration/schema/macros/input_spec.rb +102 -10
- data/spec/integration/schema/macros/maybe_spec.rb +30 -0
- data/spec/integration/schema/nested_schemas_spec.rb +200 -0
- data/spec/integration/schema/nested_values_spec.rb +3 -1
- data/spec/integration/schema/option_with_default_spec.rb +54 -20
- data/spec/integration/schema/predicates/size/fixed_spec.rb +10 -10
- data/spec/integration/schema/predicates/size/range_spec.rb +8 -10
- data/spec/unit/error_compiler_spec.rb +1 -1
- data/spec/unit/hint_compiler_spec.rb +2 -2
- metadata +18 -7
- data/examples/rule_ast.rb +0 -25
- data/lib/dry/validation/error_compiler/input.rb +0 -135
@@ -0,0 +1,200 @@
|
|
1
|
+
RSpec.describe Schema, 'nested schemas' do
|
2
|
+
context 'with a 2-level deep schema' do
|
3
|
+
subject(:schema) do
|
4
|
+
Dry::Validation.Schema do
|
5
|
+
required(:meta).schema do
|
6
|
+
required(:info).schema do
|
7
|
+
required(:details).filled
|
8
|
+
required(:meta).filled
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'passes when input is valid' do
|
15
|
+
expect(schema.(meta: { info: { details: 'Krakow', meta: 'foo' }})).to be_success
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'fails when root key is missing' do
|
19
|
+
expect(schema.({}).messages).to eql(meta: ['is missing'])
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'fails when 1-level key is missing' do
|
23
|
+
expect(schema.(meta: {}).messages).to eql(
|
24
|
+
meta: { info: ['is missing'] }
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'fails when 2-level key is missing' do
|
29
|
+
expect(schema.(meta: { info: {} }).messages).to eql(
|
30
|
+
meta: { info: { details: ['is missing'], meta: ['is missing'] } }
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'fails when 1-level key has invalid value' do
|
35
|
+
expect(schema.(meta: { info: nil, meta: 'foo' }).messages).to eql(
|
36
|
+
meta: { info: ['must be a hash'] }
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'fails when 2-level key has invalid value' do
|
41
|
+
expect(schema.(meta: { info: { details: nil, meta: 'foo' } }).messages).to eql(
|
42
|
+
meta: { info: { details: ['must be filled'] } }
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'when duplicated key names are used in 2 subsequent levels' do
|
48
|
+
subject(:schema) do
|
49
|
+
Dry::Validation.Schema do
|
50
|
+
required(:meta).schema do
|
51
|
+
required(:meta).filled
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'passes when input is valid' do
|
57
|
+
expect(schema.(meta: { meta: 'data' })).to be_success
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'fails when root key is missing' do
|
61
|
+
expect(schema.({}).messages).to eql(meta: ['is missing'])
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'fails when 1-level key is missing' do
|
65
|
+
expect(schema.(meta: {}).messages).to eql(meta: { meta: ['is missing'] })
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'fails when 1-level key value is invalid' do
|
69
|
+
expect(schema.(meta: { meta: '' }).messages).to eql(
|
70
|
+
meta: { meta: ['must be filled'] }
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'when duplicated key names are used in 2 subsequent levels as schemas' do
|
76
|
+
subject(:schema) do
|
77
|
+
Dry::Validation.Schema do
|
78
|
+
required(:meta).schema do
|
79
|
+
required(:meta).schema do
|
80
|
+
required(:data).filled
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'passes when input is valid' do
|
87
|
+
expect(schema.(meta: { meta: { data: 'this is fine' } })).to be_success
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'fails when root key is missing' do
|
91
|
+
expect(schema.({}).messages).to eql(meta: ['is missing'])
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'fails when 1-level key is missing' do
|
95
|
+
expect(schema.(meta: {}).messages).to eql(meta: { meta: ['is missing'] })
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'fails when 1-level key value is invalid' do
|
99
|
+
expect(schema.(meta: { meta: '' }).messages).to eql(
|
100
|
+
meta: { meta: ['must be a hash'] }
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'fails when 2-level key is missing' do
|
105
|
+
expect(schema.(meta: { meta: {} }).messages).to eql(
|
106
|
+
meta: { meta: { data: ['is missing'] } }
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'fails when 2-level key value is invalid' do
|
111
|
+
expect(schema.(meta: { meta: { data: '' } }).messages).to eql(
|
112
|
+
meta: { meta: { data: ['must be filled'] } }
|
113
|
+
)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'with `each` + schema inside another schema' do
|
118
|
+
subject(:schema) do
|
119
|
+
Dry::Validation.Schema do
|
120
|
+
required(:meta).schema do
|
121
|
+
required(:data).each do
|
122
|
+
schema do
|
123
|
+
required(:info).schema do
|
124
|
+
required(:name).filled
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'passes when data is valid' do
|
133
|
+
expect(schema.(meta: { data: [{ info: { name: 'oh hai' } }] })).to be_success
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'fails when root key is missing' do
|
137
|
+
expect(schema.({}).messages).to eql(meta: ['is missing'])
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'fails when root key value is invalid' do
|
141
|
+
expect(schema.(meta: '').messages).to eql(meta: ['must be a hash'])
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'fails when 1-level key is missing' do
|
145
|
+
expect(schema.(meta: {}).messages).to eql(meta: { data: ['is missing'] })
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'fails when 1-level key has invalid value' do
|
149
|
+
expect(schema.(meta: { data: '' }).messages).to eql(meta: { data: ['must be an array'] })
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'fails when 1-level key has value with a missing key' do
|
153
|
+
expect(schema.(meta: { data: [{}] }).messages).to eql(
|
154
|
+
meta: { data: { 0 => { info: ['is missing'] } } }
|
155
|
+
)
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'fails when 1-level key has value with an incorrect type' do
|
159
|
+
expect(schema.(meta: { data: [{ info: ''}] }).messages).to eql(
|
160
|
+
meta: { data: { 0 => { info: ['must be a hash'] } } }
|
161
|
+
)
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'fails when 1-level key has value with a key with an invalid value' do
|
165
|
+
expect(schema.(meta: { data: [{ info: { name: '' } }] }).messages).to eql(
|
166
|
+
meta: { data: { 0 => { info: { name: ['must be filled'] } } } }
|
167
|
+
)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'with 2-level `each` + schema' do
|
172
|
+
subject(:schema) do
|
173
|
+
Dry::Validation.Schema do
|
174
|
+
required(:data).each do
|
175
|
+
schema do
|
176
|
+
required(:tags).each do
|
177
|
+
required(:name).filled
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'passes when input is valid' do
|
185
|
+
expect(schema.(data: [{ tags: [{ name: 'red' }, { name: 'blue' }] }])).to be_success
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'fails when 1-level element is not valid' do
|
189
|
+
expect(schema.(data: [{}]).messages).to eql(
|
190
|
+
data: { 0 => { tags: ['is missing'] } }
|
191
|
+
)
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'fails when 2-level element is not valid' do
|
195
|
+
expect(schema.(data: [{ tags: [{ name: 'red' }, { name: '' }] }]).messages).to eql(
|
196
|
+
data: { 0 => { tags: { 1 => { name: ['must be filled'] } } } }
|
197
|
+
)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -37,7 +37,9 @@ RSpec.describe Schema, 'using nested values' do
|
|
37
37
|
end
|
38
38
|
|
39
39
|
it 'fails when offers is true and newsletter is true' do
|
40
|
-
|
40
|
+
input = { settings: { offers: true, newsletter: true }, email: 'jane@doe' }
|
41
|
+
|
42
|
+
expect(schema.(input).messages).to eql(
|
41
43
|
settings: { newsletter: ['must be false'] }
|
42
44
|
)
|
43
45
|
end
|
@@ -1,30 +1,64 @@
|
|
1
|
-
RSpec.describe Dry::Validation::Schema, 'defining
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
def unique?(name, value)
|
8
|
-
DB.none? { |item| item[name] == value }
|
9
|
-
end
|
10
|
-
end
|
1
|
+
RSpec.describe Dry::Validation::Schema, 'defining schema context with option API' do
|
2
|
+
shared_context 'valid schema with :db option' do
|
3
|
+
before do
|
4
|
+
DB = [{ email: 'jane@doe' }]
|
5
|
+
end
|
11
6
|
|
12
|
-
|
7
|
+
after do
|
8
|
+
Object.send(:remove_const, :DB)
|
13
9
|
end
|
14
|
-
end
|
15
10
|
|
16
|
-
|
17
|
-
|
11
|
+
it 'uses external dependency set by option with a default value' do
|
12
|
+
expect(schema.db).to be(DB)
|
13
|
+
|
14
|
+
expect(schema.(email: 'jade@doe', contact: { email: 'jade2@doe' })).to be_success
|
15
|
+
expect(schema.(email: 'jane@doe', contact: { email: 'jane2@doe' })).to be_failure
|
16
|
+
expect(schema.(email: 'jade@doe', contact: { email: 'jane@doe' })).to be_failure
|
17
|
+
end
|
18
18
|
end
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
|
21
|
+
context 'with a default value' do
|
22
|
+
subject(:schema) do
|
23
|
+
Dry::Validation.Schema do
|
24
|
+
configure do
|
25
|
+
option :db, -> { DB }
|
26
|
+
|
27
|
+
def unique?(name, value)
|
28
|
+
db.none? { |item| item[name] == value }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
required(:email).filled(unique?: :email)
|
33
|
+
|
34
|
+
required(:contact).schema do
|
35
|
+
required(:email).filled(unique?: :email)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
include_context 'valid schema with :db option'
|
22
41
|
end
|
23
42
|
|
24
|
-
|
25
|
-
|
43
|
+
context 'without a default value' do
|
44
|
+
subject(:schema) do
|
45
|
+
Dry::Validation.Schema do
|
46
|
+
configure do
|
47
|
+
option :db
|
48
|
+
|
49
|
+
def unique?(name, value)
|
50
|
+
db.none? { |item| item[name] == value }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
required(:email).filled(unique?: :email)
|
55
|
+
|
56
|
+
required(:contact).schema do
|
57
|
+
required(:email).filled(unique?: :email)
|
58
|
+
end
|
59
|
+
end.with(db: DB)
|
60
|
+
end
|
26
61
|
|
27
|
-
|
28
|
-
expect(schema.(email: 'jane@doe')).to be_failure
|
62
|
+
include_context 'valid schema with :db option'
|
29
63
|
end
|
30
64
|
end
|
@@ -35,7 +35,7 @@ RSpec.describe 'Predicates: Size' do
|
|
35
35
|
let(:input) { { foo: '' } }
|
36
36
|
|
37
37
|
#see: https://github.com/dry-rb/dry-validation/issues/121
|
38
|
-
|
38
|
+
it 'is not successful' do
|
39
39
|
expect(result).to be_failing ['length must be 3']
|
40
40
|
end
|
41
41
|
end
|
@@ -84,7 +84,7 @@ RSpec.describe 'Predicates: Size' do
|
|
84
84
|
let(:input) { { foo: '' } }
|
85
85
|
|
86
86
|
#see: https://github.com/dry-rb/dry-validation/issues/121
|
87
|
-
|
87
|
+
it 'is not successful' do
|
88
88
|
expect(result).to be_failing ['length must be 3']
|
89
89
|
end
|
90
90
|
end
|
@@ -135,7 +135,7 @@ RSpec.describe 'Predicates: Size' do
|
|
135
135
|
let(:input) { { foo: '' } }
|
136
136
|
|
137
137
|
#see: https://github.com/dry-rb/dry-validation/issues/121
|
138
|
-
|
138
|
+
it 'is not successful' do
|
139
139
|
expect(result).to be_failing ['length must be 3']
|
140
140
|
end
|
141
141
|
end
|
@@ -183,8 +183,8 @@ RSpec.describe 'Predicates: Size' do
|
|
183
183
|
context 'with blank input' do
|
184
184
|
let(:input) { { foo: '' } }
|
185
185
|
|
186
|
-
|
187
|
-
|
186
|
+
it 'is not successful' do
|
187
|
+
pending
|
188
188
|
expect(result).to be_failing ['must be filled', 'length must be 3']
|
189
189
|
end
|
190
190
|
end
|
@@ -233,7 +233,7 @@ RSpec.describe 'Predicates: Size' do
|
|
233
233
|
let(:input) { { foo: '' } }
|
234
234
|
|
235
235
|
#see: https://github.com/dry-rb/dry-validation/issues/121
|
236
|
-
|
236
|
+
it 'is not successful' do
|
237
237
|
expect(result).to be_failing ['length must be 3']
|
238
238
|
end
|
239
239
|
end
|
@@ -284,7 +284,7 @@ RSpec.describe 'Predicates: Size' do
|
|
284
284
|
let(:input) { { foo: '' } }
|
285
285
|
|
286
286
|
#see: https://github.com/dry-rb/dry-validation/issues/121
|
287
|
-
|
287
|
+
it 'is not successful' do
|
288
288
|
expect(result).to be_failing ['length must be 3']
|
289
289
|
end
|
290
290
|
end
|
@@ -332,8 +332,8 @@ RSpec.describe 'Predicates: Size' do
|
|
332
332
|
context 'with blank input' do
|
333
333
|
let(:input) { { foo: '' } }
|
334
334
|
|
335
|
-
|
336
|
-
|
335
|
+
it 'is not successful' do
|
336
|
+
pending
|
337
337
|
expect(result).to be_failing ['must be filled', 'length must be 3']
|
338
338
|
end
|
339
339
|
end
|
@@ -382,7 +382,7 @@ RSpec.describe 'Predicates: Size' do
|
|
382
382
|
let(:input) { { foo: '' } }
|
383
383
|
|
384
384
|
#see: https://github.com/dry-rb/dry-validation/issues/121
|
385
|
-
|
385
|
+
it 'is not successful' do
|
386
386
|
expect(result).to be_failing ['length must be 3']
|
387
387
|
end
|
388
388
|
end
|
@@ -34,8 +34,7 @@ RSpec.describe 'Predicates: Size' do
|
|
34
34
|
context 'with blank input' do
|
35
35
|
let(:input) { { foo: '' } }
|
36
36
|
|
37
|
-
|
38
|
-
xit 'is not successful' do
|
37
|
+
it 'is not successful' do
|
39
38
|
expect(result).to be_failing ['length must be within 2 - 3']
|
40
39
|
end
|
41
40
|
end
|
@@ -83,8 +82,7 @@ RSpec.describe 'Predicates: Size' do
|
|
83
82
|
context 'with blank input' do
|
84
83
|
let(:input) { { foo: '' } }
|
85
84
|
|
86
|
-
|
87
|
-
xit 'is not successful' do
|
85
|
+
it 'is not successful' do
|
88
86
|
expect(result).to be_failing ['length must be within 2 - 3']
|
89
87
|
end
|
90
88
|
end
|
@@ -182,8 +180,8 @@ RSpec.describe 'Predicates: Size' do
|
|
182
180
|
context 'with blank input' do
|
183
181
|
let(:input) { { foo: '' } }
|
184
182
|
|
185
|
-
|
186
|
-
|
183
|
+
it 'is not successful' do
|
184
|
+
pending
|
187
185
|
expect(result).to be_failing ['must be filled', 'length must be within 2 - 3']
|
188
186
|
end
|
189
187
|
end
|
@@ -282,7 +280,7 @@ RSpec.describe 'Predicates: Size' do
|
|
282
280
|
let(:input) { { foo: '' } }
|
283
281
|
|
284
282
|
#see: https://github.com/dry-rb/dry-validation/issues/121
|
285
|
-
|
283
|
+
it 'is not successful' do
|
286
284
|
expect(result).to be_failing ['length must be within 2 - 3']
|
287
285
|
end
|
288
286
|
end
|
@@ -330,8 +328,8 @@ RSpec.describe 'Predicates: Size' do
|
|
330
328
|
context 'with blank input' do
|
331
329
|
let(:input) { { foo: '' } }
|
332
330
|
|
333
|
-
|
334
|
-
|
331
|
+
it 'is not successful' do
|
332
|
+
pending
|
335
333
|
expect(result).to be_failing ['must be filled', 'length must be within 2 - 3']
|
336
334
|
end
|
337
335
|
end
|
@@ -380,7 +378,7 @@ RSpec.describe 'Predicates: Size' do
|
|
380
378
|
let(:input) { { foo: '' } }
|
381
379
|
|
382
380
|
#see: https://github.com/dry-rb/dry-validation/issues/121
|
383
|
-
|
381
|
+
it 'is not successful' do
|
384
382
|
expect(result).to be_failing ['length must be within 2 - 3']
|
385
383
|
end
|
386
384
|
end
|
@@ -2,6 +2,6 @@ RSpec.describe ErrorCompiler, '#call' do
|
|
2
2
|
subject(:error_compiler) { ErrorCompiler.new({}) }
|
3
3
|
|
4
4
|
it 'returns an empty hash when there are no errors' do
|
5
|
-
expect(error_compiler.([])).to
|
5
|
+
expect(error_compiler.([])).to be_empty
|
6
6
|
end
|
7
7
|
end
|
@@ -28,7 +28,7 @@ RSpec.describe HintCompiler, '#call' do
|
|
28
28
|
[:val, p(:key?, :height)],
|
29
29
|
[
|
30
30
|
:or, [
|
31
|
-
[:
|
31
|
+
[:key, [:height, p(:none?)]],
|
32
32
|
[
|
33
33
|
:and, [
|
34
34
|
[:key, [:height, p(:int?)]],
|
@@ -43,7 +43,7 @@ RSpec.describe HintCompiler, '#call' do
|
|
43
43
|
end
|
44
44
|
|
45
45
|
it 'returns hint messages for given rules' do
|
46
|
-
expect(compiler.call).to eql(
|
46
|
+
expect(compiler.call.to_h).to eql(
|
47
47
|
age: ['must be greater than 18'],
|
48
48
|
height: ['must be greater than 180'],
|
49
49
|
)
|