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
@@ -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
|