dry-validation 0.7.2 → 0.7.3
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/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:
|