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