dry-validation 0.7.2 → 0.7.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/config/errors.yml +3 -1
- data/dry-validation.gemspec +2 -1
- data/lib/dry/validation/hint_compiler.rb +5 -1
- data/lib/dry/validation/input_processor_compiler.rb +10 -2
- data/lib/dry/validation/input_processor_compiler/form.rb +3 -1
- data/lib/dry/validation/input_processor_compiler/sanitizer.rb +3 -1
- data/lib/dry/validation/messages/abstract.rb +2 -2
- data/lib/dry/validation/result.rb +1 -0
- data/lib/dry/validation/schema.rb +6 -2
- data/lib/dry/validation/schema/rule.rb +4 -2
- data/lib/dry/validation/schema_compiler.rb +4 -0
- data/lib/dry/validation/version.rb +1 -1
- data/spec/integration/error_compiler_spec.rb +1 -1
- data/spec/integration/schema/form_spec.rb +12 -10
- data/spec/integration/schema/numbers_spec.rb +19 -0
- data/spec/integration/schema/using_types_spec.rb +67 -3
- metadata +21 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a2bb0a43d4bd22192529a1b75e0bdb5d9da603f
|
4
|
+
data.tar.gz: dfd9286f07480dc94fbfbdcd6ae1007a8cfefe97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf52dea16b06ef16dee99c65dd611c044d044be0bb3df0dcf80974fb01c507764cc2ead67a87f8ee0ad215b2a0983f3fc84954cfdd1108d9a4c9d380f1bdda0f
|
7
|
+
data.tar.gz: 8bb21ccf8a81b588116d493d2f724594e77db390532aab10bada0ec4cb224d1ce504d17cbfce23e63f51edf166ad0268665e458e140b98f2c306251be751800a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
# v0.7.3 2016-03-30
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* Support for inferring rules from constrained type (coop + solnic)
|
6
|
+
* Support for inferring nested schemas from `Dry::Types::Struct` classes (coop)
|
7
|
+
* Support for `number?` predicate (solnic)
|
8
|
+
|
9
|
+
### Fixed
|
10
|
+
|
11
|
+
* Creating a nested schema properly sets full path to nested data structure (solnic)
|
12
|
+
* Error message for `empty?` predicate is now correct (jodosha)
|
13
|
+
|
14
|
+
### Internal
|
15
|
+
|
16
|
+
* Switch from `thread_safe` to `concurrent` (joevandyk)
|
17
|
+
|
18
|
+
[Compare v0.7.2...v0.7.3](https://github.com/dryrb/dry-validation/compare/v0.7.2...v0.7.3)
|
19
|
+
|
1
20
|
# v0.7.2 2016-03-28
|
2
21
|
|
3
22
|
### Added
|
data/config/errors.yml
CHANGED
@@ -2,7 +2,7 @@ en:
|
|
2
2
|
errors:
|
3
3
|
array?: "must be an array"
|
4
4
|
|
5
|
-
empty?: "
|
5
|
+
empty?: "must be empty"
|
6
6
|
|
7
7
|
exclusion?: "must not be one of: %{list}"
|
8
8
|
|
@@ -12,6 +12,8 @@ en:
|
|
12
12
|
|
13
13
|
format?: "is in invalid format"
|
14
14
|
|
15
|
+
number?: "must be a number"
|
16
|
+
|
15
17
|
gt?: "must be greater than %{num}"
|
16
18
|
|
17
19
|
gteq?: "must be greater than or equal to %{num}"
|
data/dry-validation.gemspec
CHANGED
@@ -15,10 +15,11 @@ Gem::Specification.new do |spec|
|
|
15
15
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
16
|
spec.require_paths = ['lib']
|
17
17
|
|
18
|
+
spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
|
18
19
|
spec.add_runtime_dependency 'dry-configurable', '~> 0.1', '>= 0.1.3'
|
19
20
|
spec.add_runtime_dependency 'dry-container', '~> 0.2', '>= 0.2.8'
|
20
21
|
spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
21
|
-
spec.add_runtime_dependency 'dry-logic', '~> 0.2', '>= 0.2.
|
22
|
+
spec.add_runtime_dependency 'dry-logic', '~> 0.2', '>= 0.2.2'
|
22
23
|
spec.add_runtime_dependency 'dry-types', '~> 0.6', '>= 0.6.0'
|
23
24
|
|
24
25
|
spec.add_development_dependency 'bundler'
|
@@ -24,7 +24,7 @@ module Dry
|
|
24
24
|
EXCLUDED = [:none?, :filled?, :key?].freeze
|
25
25
|
|
26
26
|
def self.cache
|
27
|
-
@cache ||=
|
27
|
+
@cache ||= Concurrent::Map.new
|
28
28
|
end
|
29
29
|
|
30
30
|
def initialize(messages, options = {})
|
@@ -114,6 +114,10 @@ module Dry
|
|
114
114
|
DEFAULT_RESULT
|
115
115
|
end
|
116
116
|
|
117
|
+
def visit_type(node)
|
118
|
+
visit(node.rule.to_ast)
|
119
|
+
end
|
120
|
+
|
117
121
|
private
|
118
122
|
|
119
123
|
def merge(result)
|
@@ -24,6 +24,14 @@ module Dry
|
|
24
24
|
send(:"visit_#{node[0]}", node[1], *args)
|
25
25
|
end
|
26
26
|
|
27
|
+
def visit_type(type, *args)
|
28
|
+
if type.is_a?(Types::Constructor)
|
29
|
+
[:constructor, [type.primitive, type.fn]]
|
30
|
+
else
|
31
|
+
DEFAULT_TYPE_NODE
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
27
35
|
def visit_schema(node, *args)
|
28
36
|
hash_node(node.input_processor_ast(identifier))
|
29
37
|
end
|
@@ -43,7 +51,7 @@ module Dry
|
|
43
51
|
if result.size == 1
|
44
52
|
result.first
|
45
53
|
else
|
46
|
-
(result - self.class::DEFAULT_TYPE_NODE).
|
54
|
+
(result - self.class::DEFAULT_TYPE_NODE).last
|
47
55
|
end
|
48
56
|
end
|
49
57
|
end
|
@@ -70,7 +78,7 @@ module Dry
|
|
70
78
|
array_node(visit(node, *args))
|
71
79
|
end
|
72
80
|
|
73
|
-
def visit_predicate(node, *
|
81
|
+
def visit_predicate(node, *)
|
74
82
|
id, args = node
|
75
83
|
|
76
84
|
if id == :key?
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'pathname'
|
2
|
-
require '
|
2
|
+
require 'concurrent/map'
|
3
3
|
|
4
4
|
module Dry
|
5
5
|
module Validation
|
@@ -36,7 +36,7 @@ module Dry
|
|
36
36
|
)
|
37
37
|
|
38
38
|
def self.cache
|
39
|
-
@cache ||=
|
39
|
+
@cache ||= Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new }
|
40
40
|
end
|
41
41
|
|
42
42
|
attr_reader :config
|
@@ -47,13 +47,17 @@ module Dry
|
|
47
47
|
|
48
48
|
def self.create_class(target, other = nil, &block)
|
49
49
|
klass =
|
50
|
-
if other
|
50
|
+
if other.is_a?(self)
|
51
51
|
Class.new(other.class)
|
52
|
+
elsif other.is_a?(Class) && other < Types::Struct
|
53
|
+
Validation.Schema(parent: target, build: false) do
|
54
|
+
other.schema.each { |attr, type| key(attr).required(type) }
|
55
|
+
end
|
52
56
|
else
|
53
57
|
Validation.Schema(target.schema_class, parent: target, build: false, &block)
|
54
58
|
end
|
55
59
|
|
56
|
-
klass.config.path =
|
60
|
+
klass.config.path = target.path if other
|
57
61
|
klass.config.input_processor = :noop
|
58
62
|
|
59
63
|
klass
|
@@ -110,8 +110,10 @@ module Dry
|
|
110
110
|
node =
|
111
111
|
if predicate.is_a?(::Symbol)
|
112
112
|
[target.type, [name, [:predicate, [predicate, args]]]]
|
113
|
-
elsif predicate.
|
114
|
-
[target.type, [name, predicate
|
113
|
+
elsif predicate.respond_to?(:rule)
|
114
|
+
[target.type, [name, [:type, predicate]]]
|
115
|
+
elsif predicate.is_a?(::Class) && predicate < ::Dry::Types::Struct
|
116
|
+
[target.type, [name, [:schema, Schema.create_class(target, predicate)]]]
|
115
117
|
else
|
116
118
|
[target.type, [name, predicate.to_ast]]
|
117
119
|
end
|
@@ -129,7 +129,7 @@ RSpec.describe Dry::Validation::ErrorCompiler do
|
|
129
129
|
[:input, [:tags, [:result, [nil, [:val, [:predicate, [:empty?, []]]]]]]]
|
130
130
|
)
|
131
131
|
|
132
|
-
expect(msg).to eql(tags: ['
|
132
|
+
expect(msg).to eql(tags: ['must be empty'])
|
133
133
|
end
|
134
134
|
end
|
135
135
|
|
@@ -9,11 +9,9 @@ RSpec.describe Dry::Validation::Schema::Form, 'defining a schema' do
|
|
9
9
|
key(:city).required
|
10
10
|
key(:street).required
|
11
11
|
|
12
|
-
key(:loc) do
|
13
|
-
|
14
|
-
|
15
|
-
key(:lng).required(:float?)
|
16
|
-
end
|
12
|
+
key(:loc).schema do
|
13
|
+
key(:lat).required(:float?)
|
14
|
+
key(:lng).required(:float?)
|
17
15
|
end
|
18
16
|
end
|
19
17
|
|
@@ -123,7 +121,7 @@ RSpec.describe Dry::Validation::Schema::Form, 'defining a schema' do
|
|
123
121
|
describe 'with nested schema in a high-level rule' do
|
124
122
|
subject(:schema) do
|
125
123
|
Dry::Validation.Form do
|
126
|
-
key(:address).maybe
|
124
|
+
key(:address).maybe(:hash?)
|
127
125
|
|
128
126
|
key(:delivery).required(:bool?)
|
129
127
|
|
@@ -144,17 +142,21 @@ RSpec.describe Dry::Validation::Schema::Form, 'defining a schema' do
|
|
144
142
|
Object.send(:remove_const, :AddressSchema)
|
145
143
|
end
|
146
144
|
|
147
|
-
it '
|
145
|
+
it 'succeeds when nested form schema succeeds' do
|
148
146
|
result = schema.(delivery: '1', address: { city: 'NYC', zipcode: '123' })
|
149
147
|
expect(result).to be_success
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'does not apply schema when there is no match' do
|
151
|
+
result = schema.(delivery: '0', address: nil)
|
152
|
+
expect(result).to be_success
|
153
|
+
end
|
150
154
|
|
155
|
+
it 'fails when nested schema fails' do
|
151
156
|
result = schema.(delivery: '1', address: { city: 'NYC', zipcode: 'foo' })
|
152
157
|
expect(result.messages).to eql(
|
153
158
|
address: { zipcode: ['must be an integer'] }
|
154
159
|
)
|
155
|
-
|
156
|
-
result = schema.(delivery: '0', address: nil)
|
157
|
-
expect(result).to be_success
|
158
160
|
end
|
159
161
|
end
|
160
162
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
RSpec.describe Dry::Validation::Schema do
|
2
|
+
subject(:schema) do
|
3
|
+
Dry::Validation.Schema do
|
4
|
+
key(:age).required(:number?, :int?)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'passes when value is a number and an int' do
|
9
|
+
expect(schema.(age: 132)).to be_success
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'fails when value is not a number' do
|
13
|
+
expect(schema.(age: 'ops').messages).to eql(age: ['must be a number'])
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'fails when value is not an integer' do
|
17
|
+
expect(schema.(age: 1.0).messages).to eql(age: ['must be an integer'])
|
18
|
+
end
|
19
|
+
end
|
@@ -3,27 +3,91 @@ RSpec.describe Dry::Validation::Schema, 'defining schema using dry types' do
|
|
3
3
|
Dry::Validation.Schema do
|
4
4
|
key(:email).required(Email)
|
5
5
|
key(:age).maybe(Age)
|
6
|
+
key(:country).required(Country)
|
7
|
+
optional(:admin).maybe(AdminBit)
|
6
8
|
end
|
7
9
|
end
|
8
10
|
|
9
11
|
before do
|
10
12
|
Email = Dry::Types['strict.string']
|
11
13
|
Age = Dry::Types['strict.int'].constrained(gt: 18)
|
14
|
+
Country = Dry::Types['strict.string'].enum('Australia', 'Poland')
|
15
|
+
AdminBit = Dry::Types['strict.bool']
|
12
16
|
end
|
13
17
|
|
14
18
|
after do
|
15
19
|
Object.send(:remove_const, :Email)
|
16
20
|
Object.send(:remove_const, :Age)
|
21
|
+
Object.send(:remove_const, :Country)
|
22
|
+
Object.send(:remove_const, :AdminBit)
|
17
23
|
end
|
18
24
|
|
19
25
|
it 'passes when input is valid' do
|
20
|
-
expect(schema.(email: 'jane@doe', age: 19)).to be_success
|
21
|
-
expect(schema.(email: 'jane@doe', age: nil)).to be_success
|
26
|
+
expect(schema.(email: 'jane@doe', age: 19, country: 'Australia')).to be_success
|
27
|
+
expect(schema.(email: 'jane@doe', age: nil, country: 'Poland')).to be_success
|
22
28
|
end
|
23
29
|
|
24
30
|
it 'fails when input is not valid' do
|
25
|
-
expect(schema.(email: '', age: 19)).to_not be_success
|
31
|
+
expect(schema.(email: '', age: 19, country: 'New Zealand')).to_not be_success
|
26
32
|
expect(schema.(email: 'jane@doe', age: 17)).to_not be_success
|
27
33
|
expect(schema.(email: 'jane@doe', age: '19')).to_not be_success
|
28
34
|
end
|
35
|
+
|
36
|
+
it 'correctly responds to messages' do
|
37
|
+
expect(schema.({}).messages).to eq(
|
38
|
+
age: ["is missing", "must be greater than 18"],
|
39
|
+
country: ["is missing", "must be one of: Australia, Poland"],
|
40
|
+
email: ["is missing", "must be String"],
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'fails when sum-type rule did not pass' do
|
45
|
+
expect(schema.(email: 'jane@doe', age: 19, country: 'Australia', admin: 'foo').messages).to eql(
|
46
|
+
admin: ['must be FalseClass', 'must be TrueClass']
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
context "structs" do
|
51
|
+
subject(:schema) do
|
52
|
+
Dry::Validation.Schema do
|
53
|
+
key(:person).required(Person)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Name < Dry::Types::Value
|
58
|
+
attribute :given_name, Dry::Types['strict.string']
|
59
|
+
attribute :family_name, Dry::Types['strict.string']
|
60
|
+
end
|
61
|
+
|
62
|
+
class Person < Dry::Types::Value
|
63
|
+
attribute :name, Name
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'handles nested structs' do
|
67
|
+
expect(schema.(person: { name: { given_name: 'Tim', family_name: 'Cooper' } })).to be_success
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'fails when input is not valid' do
|
71
|
+
expect(schema.(person: {name: {given_name: 'Tim'}}).messages).to eq(
|
72
|
+
person: { name: { family_name: ["is missing"] } }
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'custom coercions' do
|
78
|
+
subject(:schema) do
|
79
|
+
Dry::Validation.Schema do
|
80
|
+
configure { config.input_processor = :sanitizer }
|
81
|
+
|
82
|
+
key(:email).required(Dry::Types['strict.string'].constructor(&:strip))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'applies custom types to input prior validation' do
|
87
|
+
result = schema.(email: ' jane@doe.org ')
|
88
|
+
|
89
|
+
expect(result).to be_success
|
90
|
+
expect(result.to_h).to eql(email: 'jane@doe.org')
|
91
|
+
end
|
92
|
+
end
|
29
93
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-validation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Holland
|
@@ -9,8 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-03-
|
12
|
+
date: 2016-03-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: concurrent-ruby
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.0'
|
14
28
|
- !ruby/object:Gem::Dependency
|
15
29
|
name: dry-configurable
|
16
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -74,7 +88,7 @@ dependencies:
|
|
74
88
|
version: '0.2'
|
75
89
|
- - ">="
|
76
90
|
- !ruby/object:Gem::Version
|
77
|
-
version: 0.2.
|
91
|
+
version: 0.2.2
|
78
92
|
type: :runtime
|
79
93
|
prerelease: false
|
80
94
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -84,7 +98,7 @@ dependencies:
|
|
84
98
|
version: '0.2'
|
85
99
|
- - ">="
|
86
100
|
- !ruby/object:Gem::Version
|
87
|
-
version: 0.2.
|
101
|
+
version: 0.2.2
|
88
102
|
- !ruby/object:Gem::Dependency
|
89
103
|
name: dry-types
|
90
104
|
requirement: !ruby/object:Gem::Requirement
|
@@ -221,6 +235,7 @@ files:
|
|
221
235
|
- spec/integration/schema/macros/when_spec.rb
|
222
236
|
- spec/integration/schema/nested_values_spec.rb
|
223
237
|
- spec/integration/schema/not_spec.rb
|
238
|
+
- spec/integration/schema/numbers_spec.rb
|
224
239
|
- spec/integration/schema/option_with_default_spec.rb
|
225
240
|
- spec/integration/schema/reusing_schema_spec.rb
|
226
241
|
- spec/integration/schema/using_types_spec.rb
|
@@ -286,6 +301,7 @@ test_files:
|
|
286
301
|
- spec/integration/schema/macros/when_spec.rb
|
287
302
|
- spec/integration/schema/nested_values_spec.rb
|
288
303
|
- spec/integration/schema/not_spec.rb
|
304
|
+
- spec/integration/schema/numbers_spec.rb
|
289
305
|
- spec/integration/schema/option_with_default_spec.rb
|
290
306
|
- spec/integration/schema/reusing_schema_spec.rb
|
291
307
|
- spec/integration/schema/using_types_spec.rb
|
@@ -301,3 +317,4 @@ test_files:
|
|
301
317
|
- spec/unit/schema/rule_spec.rb
|
302
318
|
- spec/unit/schema/value_spec.rb
|
303
319
|
- spec/unit/schema_spec.rb
|
320
|
+
has_rdoc:
|