dry-validation 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,18 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubocop/rake_task'
|
3
|
+
|
4
|
+
Rake::Task[:default].enhance [:rubocop]
|
5
|
+
|
6
|
+
RuboCop::RakeTask.new do |task|
|
7
|
+
task.options << '--display-cop-names'
|
8
|
+
end
|
9
|
+
|
10
|
+
namespace :rubocop do
|
11
|
+
desc 'Generate a configuration file acting as a TODO list.'
|
12
|
+
task :auto_gen_config do
|
13
|
+
exec 'bundle exec rubocop --auto-gen-config'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
rescue LoadError
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
RSpec.describe Dry::Validation, 'with custom messages' do
|
2
|
+
subject(:validation) { schema.new }
|
3
|
+
|
4
|
+
describe 'defining schema' do
|
5
|
+
let(:schema) do
|
6
|
+
Class.new(Dry::Validation::Schema) do
|
7
|
+
configure do |config|
|
8
|
+
config.messages_file = SPEC_ROOT.join('fixtures/errors.yml')
|
9
|
+
config.namespace = :user
|
10
|
+
end
|
11
|
+
|
12
|
+
key(:email) { |email| email.filled? }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:attrs) do
|
17
|
+
{
|
18
|
+
email: 'jane@doe.org',
|
19
|
+
age: 19,
|
20
|
+
address: { city: 'NYC', street: 'Street 1/2', country: { code: 'US', name: 'USA' } },
|
21
|
+
phone_numbers: [
|
22
|
+
'123456', '234567'
|
23
|
+
]
|
24
|
+
}.freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#messages' do
|
28
|
+
it 'returns compiled error messages' do
|
29
|
+
expect(validation.messages(attrs.merge(email: ''))).to eql([
|
30
|
+
[:email, ["email can't be blank"]]
|
31
|
+
])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
RSpec.describe Dry::Validation do
|
2
|
+
subject(:validation) { schema.new }
|
3
|
+
|
4
|
+
shared_context 'uses custom predicates' do
|
5
|
+
it 'uses provided custom predicates' do
|
6
|
+
expect(validation.(email: 'jane@doe')).to be_empty
|
7
|
+
|
8
|
+
expect(validation.(email: nil)).to match_array([
|
9
|
+
[:error, [:input, [:email, nil, [[:val, [:email, [:predicate, [:filled?, []]]]]]]]]
|
10
|
+
])
|
11
|
+
|
12
|
+
expect(validation.(email: 'jane')).to match_array([
|
13
|
+
[:error, [:input, [:email, 'jane', [[:val, [:email, [:predicate, [:email?, []]]]]]]]]
|
14
|
+
])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'defining schema with custom predicates container' do
|
19
|
+
let(:schema) do
|
20
|
+
Class.new(Dry::Validation::Schema) do
|
21
|
+
configure do |config|
|
22
|
+
config.predicates = Test::Predicates
|
23
|
+
end
|
24
|
+
|
25
|
+
key(:email) { |value| value.filled? & value.email? }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
module Test
|
31
|
+
module Predicates
|
32
|
+
include Dry::Validation::Predicates
|
33
|
+
|
34
|
+
predicate(:email?) do |input|
|
35
|
+
input.include?('@') # for the lols
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
include_context 'uses custom predicates'
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'defining schema with custom predicate methods' do
|
45
|
+
let(:schema) do
|
46
|
+
Class.new(Dry::Validation::Schema) do
|
47
|
+
key(:email) { |value| value.filled? & value.email? }
|
48
|
+
|
49
|
+
def email?(value)
|
50
|
+
value.include?('@')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
include_context 'uses custom predicates'
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
RSpec.describe Dry::Validation do
|
2
|
+
subject(:validation) { schema.new }
|
3
|
+
|
4
|
+
describe 'defining schema' do
|
5
|
+
let(:schema) do
|
6
|
+
Class.new(Dry::Validation::Schema) do
|
7
|
+
key(:email) { |email| email.filled? }
|
8
|
+
|
9
|
+
key(:age) do |age|
|
10
|
+
age.int? & age.gt?(18)
|
11
|
+
end
|
12
|
+
|
13
|
+
key(:address) do |address|
|
14
|
+
address.key(:city) do |city|
|
15
|
+
city.min_size?(3)
|
16
|
+
end
|
17
|
+
|
18
|
+
address.key(:street) do |street|
|
19
|
+
street.filled?
|
20
|
+
end
|
21
|
+
|
22
|
+
address.key(:country) do |country|
|
23
|
+
country.key(:name, &:filled?)
|
24
|
+
country.key(:code, &:filled?)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
key(:phone_numbers) do |phone_numbers|
|
29
|
+
phone_numbers.each(&:str?)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:attrs) do
|
35
|
+
{
|
36
|
+
email: 'jane@doe.org',
|
37
|
+
age: 19,
|
38
|
+
address: { city: 'NYC', street: 'Street 1/2', country: { code: 'US', name: 'USA' } },
|
39
|
+
phone_numbers: [
|
40
|
+
'123456', '234567'
|
41
|
+
]
|
42
|
+
}.freeze
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#messages' do
|
46
|
+
it 'returns compiled error messages' do
|
47
|
+
expect(validation.messages(attrs.merge(email: ''))).to eql([
|
48
|
+
[:email, ["email must be filled"]]
|
49
|
+
])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#call' do
|
54
|
+
it 'passes when attributes are valid' do
|
55
|
+
expect(validation.(attrs)).to be_empty
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'validates presence of an email and min age value' do
|
59
|
+
expect(validation.(attrs.merge(email: '', age: 18))).to match_array([
|
60
|
+
[:error, [:input, [:age, 18, [[:val, [:age, [:predicate, [:gt?, [18]]]]]]]]],
|
61
|
+
[:error, [:input, [:email, "", [[:val, [:email, [:predicate, [:filled?, []]]]]]]]]
|
62
|
+
])
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'validates presence of the email key and type of age value' do
|
66
|
+
expect(validation.(name: 'Jane', age: '18', address: attrs[:address], phone_numbers: attrs[:phone_numbers])).to match_array([
|
67
|
+
[:error, [:input, [:age, "18", [[:val, [:age, [:predicate, [:int?, []]]]]]]]],
|
68
|
+
[:error, [:input, [:email, nil, [[:key, [:email, [:predicate, [:key?, [:email]]]]]]]]]
|
69
|
+
])
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'validates presence of the address and phone_number keys' do
|
73
|
+
expect(validation.(email: 'jane@doe.org', age: 19)).to match_array([
|
74
|
+
[:error, [:input, [:address, nil, [[:key, [:address, [:predicate, [:key?, [:address]]]]]]]]],
|
75
|
+
[:error, [:input, [:phone_numbers, nil, [[:key, [:phone_numbers, [:predicate, [:key?, [:phone_numbers]]]]]]]]]
|
76
|
+
])
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'validates presence of keys under address and min size of the city value' do
|
80
|
+
expect(validation.(attrs.merge(address: { city: 'NY' }))).to match_array([
|
81
|
+
[:error, [
|
82
|
+
:input, [
|
83
|
+
:address, {city: "NY"},
|
84
|
+
[
|
85
|
+
[:input, [:city, "NY", [[:val, [:city, [:predicate, [:min_size?, [3]]]]]]]],
|
86
|
+
[:input, [:street, nil, [[:key, [:street, [:predicate, [:key?, [:street]]]]]]]],
|
87
|
+
[:input, [:country, nil, [[:key, [:country, [:predicate, [:key?, [:country]]]]]]]]
|
88
|
+
]
|
89
|
+
]
|
90
|
+
]]
|
91
|
+
])
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'validates address code and name values' do
|
95
|
+
expect(validation.(attrs.merge(address: attrs[:address].merge(country: { code: 'US', name: '' })))).to match_array([
|
96
|
+
[:error, [
|
97
|
+
:input, [
|
98
|
+
:address, {city: "NYC", street: "Street 1/2", country: {code: "US", name: ""}},
|
99
|
+
[
|
100
|
+
[
|
101
|
+
:input, [
|
102
|
+
:country, {code: "US", name: ""}, [
|
103
|
+
[
|
104
|
+
:input, [
|
105
|
+
:name, "", [[:val, [:name, [:predicate, [:filled?, []]]]]]
|
106
|
+
]
|
107
|
+
]
|
108
|
+
]
|
109
|
+
]
|
110
|
+
]
|
111
|
+
]
|
112
|
+
]
|
113
|
+
]]
|
114
|
+
])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'dry/validation/predicates'
|
2
|
+
|
3
|
+
RSpec.shared_examples 'predicates' do
|
4
|
+
let(:nil?) { Dry::Validation::Predicates[:nil?] }
|
5
|
+
|
6
|
+
let(:str?) { Dry::Validation::Predicates[:str?] }
|
7
|
+
|
8
|
+
let(:min_size?) { Dry::Validation::Predicates[:min_size?] }
|
9
|
+
|
10
|
+
let(:key?) { Dry::Validation::Predicates[:key?] }
|
11
|
+
end
|
12
|
+
|
13
|
+
RSpec.shared_examples 'a passing predicate' do
|
14
|
+
let(:predicate) { Dry::Validation::Predicates[predicate_name] }
|
15
|
+
|
16
|
+
it do
|
17
|
+
arguments_list.each do |args|
|
18
|
+
expect(predicate.call(*args)).to be true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
RSpec.shared_examples 'a failing predicate' do
|
24
|
+
let(:predicate) { Dry::Validation::Predicates[predicate_name] }
|
25
|
+
|
26
|
+
it do
|
27
|
+
arguments_list.each do |args|
|
28
|
+
expect(predicate.call(*args)).to be false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'dry-validation'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'byebug'
|
7
|
+
rescue LoadError; end
|
8
|
+
|
9
|
+
SPEC_ROOT = Pathname(__dir__)
|
10
|
+
|
11
|
+
Dir[SPEC_ROOT.join('shared/**/*.rb')].each(&method(:require))
|
12
|
+
Dir[SPEC_ROOT.join('support/**/*.rb')].each(&method(:require))
|
13
|
+
|
14
|
+
include Dry::Validation
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.disable_monkey_patching!
|
18
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'dry/validation/messages'
|
2
|
+
require 'dry/validation/error_compiler'
|
3
|
+
|
4
|
+
RSpec.describe Dry::Validation::ErrorCompiler do
|
5
|
+
subject(:error_compiler) { ErrorCompiler.new(messages) }
|
6
|
+
|
7
|
+
let(:messages) do
|
8
|
+
Messages.default.merge(
|
9
|
+
key?: '+%{name}+ key is missing in the hash',
|
10
|
+
attributes: {
|
11
|
+
address: {
|
12
|
+
filled?: 'Please provide your address'
|
13
|
+
}
|
14
|
+
}
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#call' do
|
19
|
+
let(:ast) do
|
20
|
+
[
|
21
|
+
[:error, [:input, [:name, nil, [[:key, [:name, [:predicate, [:key?, []]]]]]]]],
|
22
|
+
[:error, [:input, [:age, 18, [[:val, [:age, [:predicate, [:gt?, [18]]]]]]]]],
|
23
|
+
[:error, [:input, [:email, "", [[:val, [:email, [:predicate, [:filled?, []]]]]]]]],
|
24
|
+
[:error, [:input, [:address, "", [[:val, [:address, [:predicate, [:filled?, []]]]]]]]]
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'converts error ast into another format' do
|
29
|
+
expect(error_compiler.(ast)).to eql([
|
30
|
+
[:name, ["+name+ key is missing in the hash"]],
|
31
|
+
[:age, ["age must be greater than 18 (18 was given)"]],
|
32
|
+
[:email, ["email must be filled"]],
|
33
|
+
[:address, ["Please provide your address"]]
|
34
|
+
])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#visit_predicate' do
|
39
|
+
describe ':empty?' do
|
40
|
+
it 'returns valid message' do
|
41
|
+
msg = error_compiler.visit_predicate([:empty?, []], [], :tags)
|
42
|
+
|
43
|
+
expect(msg).to eql('tags cannot be empty')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe ':exclusion?' do
|
48
|
+
it 'returns valid message' do
|
49
|
+
msg = error_compiler.visit_predicate([:exclusion?, [[1, 2, 3]]], 2, :num)
|
50
|
+
|
51
|
+
expect(msg).to eql('num must not be one of: 1, 2, 3')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe ':inclusion?' do
|
56
|
+
it 'returns valid message' do
|
57
|
+
msg = error_compiler.visit_predicate([:inclusion?, [[1, 2, 3]]], 2, :num)
|
58
|
+
|
59
|
+
expect(msg).to eql('num must be one of: 1, 2, 3')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe ':gt?' do
|
64
|
+
it 'returns valid message' do
|
65
|
+
msg = error_compiler.visit_predicate([:gt?, [3]], 2, :num)
|
66
|
+
|
67
|
+
expect(msg).to eql('num must be greater than 3 (2 was given)')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe ':gteq?' do
|
72
|
+
it 'returns valid message' do
|
73
|
+
msg = error_compiler.visit_predicate([:gteq?, [3]], 2, :num)
|
74
|
+
|
75
|
+
expect(msg).to eql('num must be greater than or equal to 3')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe ':lt?' do
|
80
|
+
it 'returns valid message' do
|
81
|
+
msg = error_compiler.visit_predicate([:lt?, [3]], 2, :num)
|
82
|
+
|
83
|
+
expect(msg).to eql('num must be less than 3 (2 was given)')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe ':lteq?' do
|
88
|
+
it 'returns valid message' do
|
89
|
+
msg = error_compiler.visit_predicate([:lteq?, [3]], 2, :num)
|
90
|
+
|
91
|
+
expect(msg).to eql('num must be less than or equal to 3')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe ':int?' do
|
96
|
+
it 'returns valid message' do
|
97
|
+
msg = error_compiler.visit_predicate([:int?, []], '2', :num)
|
98
|
+
|
99
|
+
expect(msg).to eql('num must be an integer')
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe ':max_size?' do
|
104
|
+
it 'returns valid message' do
|
105
|
+
msg = error_compiler.visit_predicate([:max_size?, [3]], 'abcd', :num)
|
106
|
+
|
107
|
+
expect(msg).to eql('num size cannot be greater than 3')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe ':min_size?' do
|
112
|
+
it 'returns valid message' do
|
113
|
+
msg = error_compiler.visit_predicate([:min_size?, [3]], 'ab', :num)
|
114
|
+
|
115
|
+
expect(msg).to eql('num size cannot be less than 3')
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe ':nil?' do
|
120
|
+
it 'returns valid message' do
|
121
|
+
msg = error_compiler.visit_predicate([:nil?, []], nil, :num)
|
122
|
+
|
123
|
+
expect(msg).to eql('num cannot be nil')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe ':size?' do
|
128
|
+
it 'returns valid message when arg is int' do
|
129
|
+
msg = error_compiler.visit_predicate([:size?, [3]], 'ab', :num)
|
130
|
+
|
131
|
+
expect(msg).to eql('num size must be 3')
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'returns valid message when arg is range' do
|
135
|
+
msg = error_compiler.visit_predicate([:size?, [3..4]], 'ab', :num)
|
136
|
+
|
137
|
+
expect(msg).to eql('num size must be within 3 - 4')
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe ':str?' do
|
142
|
+
it 'returns valid message' do
|
143
|
+
msg = error_compiler.visit_predicate([:str?, []], 3, :num)
|
144
|
+
|
145
|
+
expect(msg).to eql('num must be a string')
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe ':format?' do
|
150
|
+
it 'returns valid message' do
|
151
|
+
msg = error_compiler.visit_predicate([:format?, [/^F/]], 'Bar', :str)
|
152
|
+
|
153
|
+
expect(msg).to eql('str is in invalid format')
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe ':eql?' do
|
158
|
+
it 'returns valid message' do
|
159
|
+
msg = error_compiler.visit_predicate([:eql?, ['Bar']], 'Foo', :str)
|
160
|
+
|
161
|
+
expect(msg).to eql('str must be equal to Bar')
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|