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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3c372ced33f2c8865c3ecac3fe917610dc414d89
4
- data.tar.gz: 50e61628de4b84caf57bcc874fbf74a4ba3925b8
3
+ metadata.gz: 2a2bb0a43d4bd22192529a1b75e0bdb5d9da603f
4
+ data.tar.gz: dfd9286f07480dc94fbfbdcd6ae1007a8cfefe97
5
5
  SHA512:
6
- metadata.gz: 2a79910c2cd2e54c8076ca4b218976d972d06f99db6063005d57d7c9e47f96433bd380a53c4504a76c41cc4a810706fb383e3c298c2122416578276626b1fa2b
7
- data.tar.gz: d731cd50af1a2b58ac03baccb1706fb2cb66acbb98ae4931f363d19fb13eebada0b766d8030a3404efe5a30d43264805a69321518467777c3a8f639b2f0e5596
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?: "cannot be 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}"
@@ -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.1'
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 ||= ThreadSafe::Cache.new
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).first
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, *args)
81
+ def visit_predicate(node, *)
74
82
  id, args = node
75
83
 
76
84
  if id == :key?
@@ -11,7 +11,9 @@ module Dry
11
11
  decimal?: 'form.decimal',
12
12
  date?: 'form.date',
13
13
  date_time?: 'form.date_time',
14
- time?: 'form.time'
14
+ time?: 'form.time',
15
+ hash?: 'form.hash',
16
+ array?: 'form.array'
15
17
  }.freeze
16
18
 
17
19
  CONST_MAP = {
@@ -11,7 +11,9 @@ module Dry
11
11
  decimal?: 'decimal',
12
12
  date?: 'date',
13
13
  date_time?: 'date_time',
14
- time?: 'time'
14
+ time?: 'time',
15
+ hash?: 'hash',
16
+ array?: 'array'
15
17
  }.freeze
16
18
 
17
19
  CONST_MAP = {
@@ -1,5 +1,5 @@
1
1
  require 'pathname'
2
- require 'thread_safe/cache'
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 ||= ThreadSafe::Cache.new { |h, k| h[k] = ThreadSafe::Cache.new }
39
+ @cache ||= Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new }
40
40
  end
41
41
 
42
42
  attr_reader :config
@@ -10,6 +10,7 @@ module Dry
10
10
  attr_reader :hint_compiler
11
11
 
12
12
  alias_method :to_hash, :output
13
+ alias_method :to_h, :output # for MRI 2.0, remove it when drop support
13
14
 
14
15
  EMPTY_MESSAGES = {}.freeze
15
16
 
@@ -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 = [target.name] if other
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.is_a?(Types::Constrained)
114
- [target.type, [name, predicate.rule.to_ast]]
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
@@ -33,6 +33,10 @@ module Dry
33
33
  deps, other = node
34
34
  Guard.new(visit(other), deps)
35
35
  end
36
+
37
+ def visit_type(type)
38
+ type.rule
39
+ end
36
40
  end
37
41
  end
38
42
  end
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module Validation
3
- VERSION = '0.7.2'.freeze
3
+ VERSION = '0.7.3'.freeze
4
4
  end
5
5
  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: ['cannot be empty'])
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
- hash? do
14
- key(:lat).required(:float?)
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 'applies nested form schema' do
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.2
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-28 00:00:00.000000000 Z
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.1
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.1
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: