dry-validation 1.1.1 → 1.4.0

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
  SHA256:
3
- metadata.gz: 3821e92d2dced61bb892e20a5e5d69b596128fcdbd05596e41fff8716767b08c
4
- data.tar.gz: f797d5e46604e777966cb3d9a7a4032265227684164224a8ff109fc8ba51d1c1
3
+ metadata.gz: 63194a473d9c41ac3d26a861eeb4b83bee43196864439646cdb62fca01c6ac68
4
+ data.tar.gz: da3ec5f48cc07869828c9c692259e36fe199801a4c7573abaee67a3340928347
5
5
  SHA512:
6
- metadata.gz: 7504498b77c794b9fe37e0384fba50a8fb6c99e3fecd3423e66fe018006288b3ba1a5a7a2232fb53dede256ba0a13630313748a0214b4a0d9865fc22c0b9edae
7
- data.tar.gz: 654233d365e7844985f465e16335a234a2d5c2c240b97a485d286a52e6229e3ee80d8719e76d3ec95fe393d75f480617452b7813cb36b4b033021f9b98958461
6
+ metadata.gz: 5f523435436ea0c17789d40a2dba7fef627cab4dfcd2f1f2955c2a0b8f2a9a98d144e0ea3e821d07830bd100857fb20d18d9e6c6225e361193e4b0d6579613de
7
+ data.tar.gz: 93a13f949010dc5bbd47e1608ba43dcb6d18cea091c3ec8b5706e5d80bae4b6beecfc57312db83ec1dd1f3a8a9f26fa3c8b290307e7ba331f09c5d555d66c78d
@@ -1,8 +1,79 @@
1
+ # v1.4.0 2019-12-12
2
+
3
+ ### Added
4
+
5
+ - Support for multi-schema inheritance (@ianwhite)
6
+
7
+ ### Fixed
8
+
9
+ - Keyword warnings reported by Ruby 2.7 (@flash-gordon)
10
+ - Fixed an issue where `MessageSet` would be marked as empty too early (@ianwhite)
11
+ - Messages are correctly generated when there are errors for both an array and one or more of its elements (see #599) (@Bugagazavr)
12
+
13
+ ### Changed
14
+
15
+ - A meaningful exception is raised when failure options are not valid (@MatElGran)
16
+ - [internal] improved performance in `Contract.ensure_valid_keys` (@grzegorz-jakubiak)
17
+ - [internal] fixed keyword warnings on MRI 2.7.0 (@flash-gordon)
18
+
19
+ [Compare v1.3.1...master](https://github.com/dry-rb/dry-validation/compare/v1.3.1...master)
20
+
21
+ # v1.3.1 2019-08-16
22
+
23
+ ### Changed
24
+
25
+ - You can now set an external schema without providing a block (@alassek)
26
+
27
+ [Compare v1.3.0...v1.3.1](https://github.com/dry-rb/dry-validation/compare/v1.3.0...v1.3.1)
28
+
29
+ # v1.3.0 2019-08-14
30
+
31
+ ### Added
32
+
33
+ - Support for setting an external schema (that can be extended too) (fixed #574) (@solnic)
34
+
35
+ ### Fixed
36
+
37
+ - Using a hash spec to define rule keys with more than one value is properly handled by rule guard now (fixed #576) (@solnic)
38
+
39
+ ### Changed
40
+
41
+ - `values` within rules uses `Hash#fetch_values` internally now, which improves performance (@esparta)
42
+
43
+ [Compare v1.2.1...v1.3.0](https://github.com/dry-rb/dry-validation/compare/v1.2.1...v1.3.0)
44
+
45
+ # v1.2.1 2019-07-16
46
+
47
+ ### Fixed
48
+
49
+ - Defining an abstract contract class that has no schema no longer crashes (issue #565) (@solnic)
50
+ - Fixed an issue where `Rule#each` would crash when the value is not an array (issue #567) (@solnic)
51
+ - Fixed an issue where guarding a rule would crash when keys are missing in the input (issue #569) (@solnic)
52
+ - Added missing "pathname" require (issue #570) (@solnic)
53
+
54
+ [Compare v1.2.0...v1.2.1](https://github.com/dry-rb/dry-validation/compare/v1.2.0...v1.2.1)
55
+
56
+ # v1.2.0 2019-07-08
57
+
58
+ ### Added
59
+
60
+ - New extension `:predicates_as_macros` (@waiting-for-dev)
61
+
62
+ ### Fixed
63
+
64
+ - Guarding rules for nested keys works correctly (issue #560) (@solnic)
65
+
66
+ ### Changed
67
+
68
+ - `dry-schema` dependency was bumped to `>= 1.3.1` (@solnic)
69
+
70
+ [Compare v1.1.1...v1.2.0](https://github.com/dry-rb/dry-validation/compare/v1.1.1...v1.2.0)
71
+
1
72
  # v1.1.1 2019-06-24
2
73
 
3
74
  ### Fixed
4
75
 
5
- * `Rule#each` works with array values from nested hashes (@mustardnoise)
76
+ - `Rule#each` works with array values from nested hashes (@mustardnoise)
6
77
 
7
78
  [Compare v1.1.0...v1.1.1](https://github.com/dry-rb/dry-validation/compare/v1.1.0...v1.1.1)
8
79
 
@@ -10,13 +81,13 @@
10
81
 
11
82
  ### Added
12
83
 
13
- * `key?` method available within rules, that can be used to check if there's a value under the rule's default key (refs #540) (@solnic)
14
- * `value` supports hash-based path specifications now (refs #547) (@solnic)
15
- * `value` can read multiple values when the key points to them, ie in case of `rule(geo: [:lat, :lon])` it would return an array with `lat` and `lon` (@solnic)
84
+ - `key?` method available within rules, that can be used to check if there's a value under the rule's default key (refs #540) (@solnic)
85
+ - `value` supports hash-based path specifications now (refs #547) (@solnic)
86
+ - `value` can read multiple values when the key points to them, ie in case of `rule(geo: [:lat, :lon])` it would return an array with `lat` and `lon` (@solnic)
16
87
 
17
88
  ### Fixed
18
89
 
19
- * Passing multiple macro names to `validate` or `each` works correctly (fixed #538 #541) (@jandudulski)
90
+ - Passing multiple macro names to `validate` or `each` works correctly (fixed #538 #541) (@jandudulski)
20
91
 
21
92
  [Compare v1.0.0...v1.1.0](https://github.com/dry-rb/dry-validation/compare/v1.0.0...v1.1.0)
22
93
 
@@ -63,14 +134,14 @@ See [the list of all addressed issues](https://github.com/dry-rb/dry-validation/
63
134
 
64
135
  ### Added
65
136
 
66
- * [EXPERIMENTAL] `Validation.register_macro` for registering global macros (solnic)
67
- * [EXPERIMENTAL] `Contract.register_macro` for registering macros available to specific contract classes (solnic)
68
- * `Dry::Validation.Contract` shortcut for quickly defining a contract and getting its instance back (solnic)
69
- * New configuration option `config.locale` for setting the default locale (solnic)
137
+ - [EXPERIMENTAL] `Validation.register_macro` for registering global macros (solnic)
138
+ - [EXPERIMENTAL] `Contract.register_macro` for registering macros available to specific contract classes (solnic)
139
+ - `Dry::Validation.Contract` shortcut for quickly defining a contract and getting its instance back (solnic)
140
+ - New configuration option `config.locale` for setting the default locale (solnic)
70
141
 
71
142
  ### Fixed
72
143
 
73
- * `config/errors.yml` are now bundled with the gem, **`rc2` was broken because of this** (solnic)
144
+ - `config/errors.yml` are now bundled with the gem, **`rc2` was broken because of this** (solnic)
74
145
 
75
146
  [Compare v1.0.0.rc2...v1.0.0.rc3](https://github.com/dry-rb/dry-validation/compare/v1.0.0.rc2...v1.0.0.rc3)
76
147
 
@@ -80,17 +151,17 @@ This was **yanked** on rubygems.org because the bundled gem was missing `config`
80
151
 
81
152
  ### Added
82
153
 
83
- * [EXPERIMENTAL] support for registering macros via `Dry::Validation::Macros.register(:your_macro, &block)` (solnic)
84
- * [EXPERIMENTAL] `:acceptance` as the first built-in macro (issue #157) (solnic)
154
+ - [EXPERIMENTAL] support for registering macros via `Dry::Validation::Macros.register(:your_macro, &block)` (solnic)
155
+ - [EXPERIMENTAL] `:acceptance` as the first built-in macro (issue #157) (solnic)
85
156
 
86
157
  ### Fixed
87
158
 
88
- * Passing invalid argument to `failure` will raise a meaningful error instead of crashing (solnic)
159
+ - Passing invalid argument to `failure` will raise a meaningful error instead of crashing (solnic)
89
160
 
90
161
  ### Changed
91
162
 
92
- * In rule validation blocks, `values` is now an instance of a hash-like `Dry::Validation::Values` class, rather than `Dry::Schema::Result`. This gives more convenient access to data within rules (solnic)
93
- * Dependency on `dry-schema` was updated to `~> 1.0` (solnic)
163
+ - In rule validation blocks, `values` is now an instance of a hash-like `Dry::Validation::Values` class, rather than `Dry::Schema::Result`. This gives more convenient access to data within rules (solnic)
164
+ - Dependency on `dry-schema` was updated to `~> 1.0` (solnic)
94
165
 
95
166
  [Compare v1.0.0.rc1...v1.0.0.rc2](https://github.com/dry-rb/dry-validation/compare/v1.0.0.rc1...v1.0.0.rc2)
96
167
 
@@ -98,18 +169,18 @@ This was **yanked** on rubygems.org because the bundled gem was missing `config`
98
169
 
99
170
  ### Added
100
171
 
101
- * `:hints` extension is back (solnic)
102
- * `Result` objects have access to the context object which is shared between rules (flash-gordon)
172
+ - `:hints` extension is back (solnic)
173
+ - `Result` objects have access to the context object which is shared between rules (flash-gordon)
103
174
 
104
175
  ### Fixed
105
176
 
106
- * Multiple hint messages no longer crash message set (flash-gordon)
107
- * `Contract#inspect` no longer crashes (solnic)
177
+ - Multiple hint messages no longer crash message set (flash-gordon)
178
+ - `Contract#inspect` no longer crashes (solnic)
108
179
 
109
180
  ### Changed
110
181
 
111
- * Dependency on `dry-schema` was bumped to `~> 0.6` - this pulls in `dry-types 1.0.0` and `dry-logic 1.0.0` (solnic)
112
- * Dependency on `dry-initializer` was bumped to `~> 3.0` (solnic)
182
+ - Dependency on `dry-schema` was bumped to `~> 0.6` - this pulls in `dry-types 1.0.0` and `dry-logic 1.0.0` (solnic)
183
+ - Dependency on `dry-initializer` was bumped to `~> 3.0` (solnic)
113
184
 
114
185
  [Compare v1.0.0.beta2...v1.0.0.rc1](https://github.com/dry-rb/dry-validation/compare/v1.0.0.beta2...v1.0.0.rc1)
115
186
 
@@ -117,7 +188,7 @@ This was **yanked** on rubygems.org because the bundled gem was missing `config`
117
188
 
118
189
  ### Added
119
190
 
120
- * Support for arbitrary meta-data in failures, ie:
191
+ - Support for arbitrary meta-data in failures, ie:
121
192
 
122
193
  ```ruby
123
194
  class NewUserContract < Dry::Validation::Contract
@@ -135,9 +206,9 @@ This was **yanked** on rubygems.org because the bundled gem was missing `config`
135
206
 
136
207
  ### Changed
137
208
 
138
- * [BREAKING] `Error` was renamed to `Message` as it is a more generic concept (solnic)
139
- * [BREAKING] `ErrorSet` was renamed to `MessageSet` for consistency (solnic)
140
- * [BREAKING] `:monads` extension wraps entire result objects in `Success` or `Failure` (flash-gordon)
209
+ - [BREAKING] `Error` was renamed to `Message` as it is a more generic concept (solnic)
210
+ - [BREAKING] `ErrorSet` was renamed to `MessageSet` for consistency (solnic)
211
+ - [BREAKING] `:monads` extension wraps entire result objects in `Success` or `Failure` (flash-gordon)
141
212
 
142
213
  [Compare v1.0.0.beta1...v1.0.0.beta2](https://github.com/dry-rb/dry-validation/compare/v1.0.0.beta1...v1.0.0.beta2)
143
214
 
@@ -145,20 +216,20 @@ This was **yanked** on rubygems.org because the bundled gem was missing `config`
145
216
 
146
217
  ### Added
147
218
 
148
- * New API for setting failures `base.failure` for base errors and `key.failure` for key errors (solnic)
149
- * Support for `base` errors associated with a key even when child keys have errors too (solnic)
150
- * Support for `base` errors not associated with any key (solnic)
151
- * Result objects use `ErrorSet` object now for managing messages (solnic)
152
- * Nested keys are properly handled when generating messages hash (issue #489) (flash-gordon + solnic)
153
- * Result objects support `locale` and `full` options now (solnic)
154
- * Ability to configure `top_namespace` for messages, which will be used for both schema and rule localization (solnic)
155
- * Rule blocks receive a context object that you can use to share data between rules (solnic)
219
+ - New API for setting failures `base.failure` for base errors and `key.failure` for key errors (solnic)
220
+ - Support for `base` errors associated with a key even when child keys have errors too (solnic)
221
+ - Support for `base` errors not associated with any key (solnic)
222
+ - Result objects use `ErrorSet` object now for managing messages (solnic)
223
+ - Nested keys are properly handled when generating messages hash (issue #489) (flash-gordon + solnic)
224
+ - Result objects support `locale` and `full` options now (solnic)
225
+ - Ability to configure `top_namespace` for messages, which will be used for both schema and rule localization (solnic)
226
+ - Rule blocks receive a context object that you can use to share data between rules (solnic)
156
227
 
157
228
  ### Changed
158
229
 
159
- * [BREAKING] `Result#errors` returns an instance of `ErrorSet` now, it's an enumerable, coerible to a hash (solnic)
160
- * [BREAKING] `failure` was removed in favor of `key.failure` or `key(:foo).failure` (solnic)
161
- * [BREAKING] `Result#to_hash` was removed (flash-gordon)
230
+ - [BREAKING] `Result#errors` returns an instance of `ErrorSet` now, it's an enumerable, coerible to a hash (solnic)
231
+ - [BREAKING] `failure` was removed in favor of `key.failure` or `key(:foo).failure` (solnic)
232
+ - [BREAKING] `Result#to_hash` was removed (flash-gordon)
162
233
 
163
234
  [Compare v1.0.0.alpha2...v1.0.0.beta1](https://github.com/dry-rb/dry-validation/compare/v1.0.0.alpha2...v1.0.0.beta1)
164
235
 
@@ -168,13 +239,13 @@ First round of bug fixes. Thanks for testing <3!
168
239
 
169
240
  ### Fixed
170
241
 
171
- * Errors with nested messages are correctly built (flash-gordon)
172
- * Messages for nested keys are correctly resolved (solnic)
173
- * A message for a nested key is resolved when it's defined under `errors.rule.%{key}` too, but a message under nested key will override it (solnic)
242
+ - Errors with nested messages are correctly built (flash-gordon)
243
+ - Messages for nested keys are correctly resolved (solnic)
244
+ - A message for a nested key is resolved when it's defined under `errors.rule.%{key}` too, but a message under nested key will override it (solnic)
174
245
 
175
246
  ### Changed
176
247
 
177
- * When a message template is not found a more meaningful error is raised that includes both rule identifier and key path (solnic)
248
+ - When a message template is not found a more meaningful error is raised that includes both rule identifier and key path (solnic)
178
249
 
179
250
  [Compare v1.0.0.alpha1...v1.0.0.alpha2](https://github.com/dry-rb/dry-validation/compare/v1.0.0.alpha1...v1.0.0.alpha2)
180
251
 
@@ -184,8 +255,8 @@ Complete rewrite on top of `dry-schema`.
184
255
 
185
256
  ### Added
186
257
 
187
- * [BREAKING] `Dry::Validation::Contract` as a replacement for validation schemas (solnic)
188
- * [BREAKING] New `rule` DSL with an improved API for setting error messages (solnic)
258
+ - [BREAKING] `Dry::Validation::Contract` as a replacement for validation schemas (solnic)
259
+ - [BREAKING] New `rule` DSL with an improved API for setting error messages (solnic)
189
260
 
190
261
  [Compare v0.13.0...v1.0.0.alpha1](https://github.com/dry-rb/dry-validation/compare/v0.13.0...v1.0.0.alpha1)
191
262
 
@@ -206,7 +277,7 @@ Complete rewrite on top of `dry-schema`.
206
277
 
207
278
  ### Changed
208
279
 
209
- * [internal] dry-logic was pinned to `~> 0.4.2` (flash-gordon)
280
+ - [internal] dry-logic was pinned to `~> 0.4.2` (flash-gordon)
210
281
 
211
282
  [Compare v0.12.2...v0.12.3](https://github.com/dry-rb/dry-validation/compare/v0.12.2...v0.12.3)
212
283
 
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  [gem]: https://rubygems.org/gems/dry-validation
2
- [travis]: https://travis-ci.org/dry-rb/dry-validation
2
+ [ci]: https://github.com/dry-rb/dry-validation/actions?query=workflow%3Aci
3
3
  [codeclimate]: https://codeclimate.com/github/dry-rb/dry-validation
4
4
  [chat]: https://dry-rb.zulipchat.com
5
5
  [inchpages]: http://inch-ci.org/github/dry-rb/dry-validation
@@ -7,23 +7,23 @@
7
7
  # dry-validation [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
8
8
 
9
9
  [![Gem Version](https://badge.fury.io/rb/dry-validation.svg)][gem]
10
- [![Build Status](https://travis-ci.org/dry-rb/dry-validation.svg?branch=master)][travis]
10
+ [![Build Status](https://github.com/dry-rb/dry-monads/workflows/ci/badge.svg)][ci]
11
11
  [![Code Climate](https://codeclimate.com/github/dry-rb/dry-validation/badges/gpa.svg)][codeclimate]
12
12
  [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-validation/badges/coverage.svg)][codeclimate]
13
13
  [![Inline docs](http://inch-ci.org/github/dry-rb/dry-validation.svg?branch=master)][inchpages]
14
14
 
15
15
  ## Links
16
16
 
17
- * [User documentation](https://dry-rb.org/gems/dry-validation)
18
- * [API documentation](http://rubydoc.info/gems/dry-validation)
19
- * [Guidelines for contributing](CONTRIBUTING.md)
17
+ - [User documentation](https://dry-rb.org/gems/dry-validation)
18
+ - [API documentation](http://rubydoc.info/gems/dry-validation)
19
+ - [Guidelines for contributing](CONTRIBUTING.md)
20
20
 
21
21
  ## Supported Ruby versions
22
22
 
23
- This library officially supports following Ruby versions:
23
+ This library officially supports the following Ruby versions:
24
24
 
25
- * MRI >= `2.4`
26
- * jruby >= `9.2`
25
+ - MRI >= `2.4`
26
+ - jruby >= `9.2`
27
27
 
28
28
  ## License
29
29
 
@@ -23,6 +23,10 @@ module Dry
23
23
  require 'dry/validation/extensions/hints'
24
24
  end
25
25
 
26
+ register_extension(:predicates_as_macros) do
27
+ require 'dry/validation/extensions/predicates_as_macros'
28
+ end
29
+
26
30
  # Define a contract and build its instance
27
31
  #
28
32
  # @example
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
3
4
  require 'dry/core/constants'
4
5
 
5
6
  module Dry
@@ -98,7 +98,7 @@ module Dry
98
98
  rule_result = rule.(self, result)
99
99
 
100
100
  rule_result.failures.each do |failure|
101
- result.add_error(message_resolver[failure])
101
+ result.add_error(message_resolver.(**failure))
102
102
  end
103
103
  end
104
104
  end
@@ -116,11 +116,26 @@ module Dry
116
116
  private
117
117
 
118
118
  # @api private
119
- def error?(result, key)
120
- path = Schema::Path[key]
119
+ def error?(result, spec)
120
+ path = Schema::Path[spec]
121
121
 
122
- result.error?(path) ||
123
- path.map.with_index { |_k, i| result.error?(path.keys[0..i - 2]) }.any?
122
+ if path.multi_value?
123
+ return path.expand.any? { |nested_path| error?(result, nested_path) }
124
+ end
125
+
126
+ return true if result.error?(path)
127
+
128
+ path
129
+ .to_a[0..-2]
130
+ .any? { |key|
131
+ curr_path = Schema::Path[path.keys[0..path.keys.index(key)]]
132
+
133
+ return false unless result.error?(curr_path)
134
+
135
+ result.errors.any? { |err|
136
+ (other = Schema::Path[err.path]).same_root?(curr_path) && other == curr_path
137
+ }
138
+ }
124
139
  end
125
140
 
126
141
  # Get a registered macro
@@ -7,34 +7,9 @@ require 'dry/schema/key_map'
7
7
 
8
8
  require 'dry/validation/constants'
9
9
  require 'dry/validation/macros'
10
+ require 'dry/validation/schema_ext'
10
11
 
11
12
  module Dry
12
- module Schema
13
- # @api private
14
- class Key
15
- # @api private
16
- def to_dot_notation
17
- [name.to_s]
18
- end
19
-
20
- # @api private
21
- class Hash < Key
22
- # @api private
23
- def to_dot_notation
24
- [name].product(members.map(&:to_dot_notation).flatten(1)).map { |e| e.join(DOT) }
25
- end
26
- end
27
- end
28
-
29
- # @api private
30
- class KeyMap
31
- # @api private
32
- def to_dot_notation
33
- @to_dot_notation ||= map(&:to_dot_notation).flatten
34
- end
35
- end
36
- end
37
-
38
13
  module Validation
39
14
  class Contract
40
15
  # Contract's class interface
@@ -78,36 +53,36 @@ module Dry
78
53
  #
79
54
  # This type of schema is suitable for HTTP parameters
80
55
  #
81
- # @return [Dry::Schema::Params]
56
+ # @return [Dry::Schema::Params,NilClass]
82
57
  # @see https://dry-rb.org/gems/dry-schema/params/
83
58
  #
84
59
  # @api public
85
- def params(&block)
86
- define(:Params, &block)
60
+ def params(*external_schemas, &block)
61
+ define(:Params, external_schemas, &block)
87
62
  end
88
63
 
89
64
  # Define a JSON schema for your contract
90
65
  #
91
66
  # This type of schema is suitable for JSON data
92
67
  #
93
- # @return [Dry::Schema::JSON]
68
+ # @return [Dry::Schema::JSON,NilClass]
94
69
  # @see https://dry-rb.org/gems/dry-schema/json/
95
70
  #
96
71
  # @api public
97
- def json(&block)
98
- define(:JSON, &block)
72
+ def json(*external_schemas, &block)
73
+ define(:JSON, external_schemas, &block)
99
74
  end
100
75
 
101
76
  # Define a plain schema for your contract
102
77
  #
103
78
  # This type of schema does not offer coercion out of the box
104
79
  #
105
- # @return [Dry::Schema::Processor]
80
+ # @return [Dry::Schema::Processor,NilClass]
106
81
  # @see https://dry-rb.org/gems/dry-schema/
107
82
  #
108
83
  # @api public
109
- def schema(&block)
110
- define(:schema, &block)
84
+ def schema(*external_schemas, &block)
85
+ define(:schema, external_schemas, &block)
111
86
  end
112
87
 
113
88
  # Define a rule for your contract
@@ -126,7 +101,7 @@ module Dry
126
101
  #
127
102
  # @api public
128
103
  def rule(*keys, &block)
129
- ensure_valid_keys(*keys)
104
+ ensure_valid_keys(*keys) if __schema__
130
105
 
131
106
  Rule.new(keys: keys, block: block).tap do |rule|
132
107
  rules << rule
@@ -148,7 +123,7 @@ module Dry
148
123
  #
149
124
  # @api public
150
125
  def build(options = EMPTY_HASH, &block)
151
- Class.new(self, &block).new(options)
126
+ Class.new(self, &block).new(**options)
152
127
  end
153
128
 
154
129
  # @api private
@@ -179,12 +154,28 @@ module Dry
179
154
  private
180
155
 
181
156
  # @api private
157
+ # rubocop:disable Metrics/AbcSize
182
158
  def ensure_valid_keys(*keys)
183
159
  valid_paths = key_map.to_dot_notation.map { |value| Schema::Path[value] }
184
160
 
185
- invalid_keys = Schema::KeyMap[*keys]
186
- .map(&:dump)
187
- .reject { |spec| valid_paths.any? { |path| path.include?(Schema::Path[spec]) } }
161
+ invalid_keys = keys
162
+ .map { |key|
163
+ [key, Schema::Path[key]]
164
+ }
165
+ .flat_map { |(key, path)|
166
+ if (last = path.last).is_a?(Array)
167
+ last.map { |last_key|
168
+ path_key = [*path.to_a[0..-2], last_key]
169
+ [path_key, Schema::Path[path_key]]
170
+ }
171
+ else
172
+ [[key, path]]
173
+ end
174
+ }
175
+ .reject { |(_, path)|
176
+ valid_paths.any? { |valid_path| valid_path.include?(path) }
177
+ }
178
+ .map(&:first)
188
179
 
189
180
  return if invalid_keys.empty?
190
181
 
@@ -192,6 +183,7 @@ module Dry
192
183
  #{name}.rule specifies keys that are not defined by the schema: #{invalid_keys.inspect}
193
184
  STR
194
185
  end
186
+ # rubocop:enable Metrics/AbcSize
195
187
 
196
188
  # @api private
197
189
  def key_map
@@ -199,23 +191,29 @@ module Dry
199
191
  end
200
192
 
201
193
  # @api private
202
- def schema_opts
194
+ def core_schema_opts
203
195
  { parent: superclass&.__schema__, config: config }
204
196
  end
205
197
 
206
198
  # @api private
207
- def define(method_name, &block)
208
- if defined?(@__schema__)
199
+ def define(method_name, external_schemas, &block)
200
+ return __schema__ if external_schemas.empty? && block.nil?
201
+
202
+ unless __schema__.nil?
209
203
  raise ::Dry::Validation::DuplicateSchemaError, 'Schema has already been defined'
210
204
  end
211
205
 
206
+ schema_opts = core_schema_opts
207
+
208
+ schema_opts.update(parent: external_schemas) if external_schemas.any?
209
+
212
210
  case method_name
213
211
  when :schema
214
- @__schema__ = Schema.define(schema_opts, &block)
212
+ @__schema__ = Schema.define(**schema_opts, &block)
215
213
  when :Params
216
- @__schema__ = Schema.Params(schema_opts, &block)
214
+ @__schema__ = Schema.Params(**schema_opts, &block)
217
215
  when :JSON
218
- @__schema__ = Schema.JSON(schema_opts, &block)
216
+ @__schema__ = Schema.JSON(**schema_opts, &block)
219
217
  end
220
218
  end
221
219
  end
@@ -63,19 +63,19 @@ module Dry
63
63
  # Initialize a new evaluator
64
64
  #
65
65
  # @api private
66
- def initialize(contract, options, &block)
67
- super(contract, options)
66
+ def initialize(contract, **options, &block)
67
+ super(contract, **options)
68
68
 
69
69
  @_options = options
70
70
 
71
71
  if block
72
72
  exec_opts = block_options.map { |key, value| [key, _options[value]] }.to_h
73
- instance_exec(exec_opts, &block)
73
+ instance_exec(**exec_opts, &block)
74
74
  end
75
75
 
76
76
  macros.each do |args|
77
77
  macro = macro(*args.flatten(1))
78
- instance_exec(macro.extract_block_options(_options.merge(macro: macro)), &macro.block)
78
+ instance_exec(**macro.extract_block_options(_options.merge(macro: macro)), &macro.block)
79
79
  end
80
80
  end
81
81
 
@@ -117,7 +117,7 @@ module Dry
117
117
 
118
118
  # @api private
119
119
  def with(new_opts, &block)
120
- self.class.new(_contract, _options.merge(new_opts), &block)
120
+ self.class.new(_contract, **_options, **new_opts, &block)
121
121
  end
122
122
 
123
123
  # Return default (first) key name
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/schema/predicate_registry'
4
+ require 'dry/validation/contract'
5
+
6
+ module Dry
7
+ module Validation
8
+ # Predicate registry with additional needed methods.
9
+ class PredicateRegistry < Schema::PredicateRegistry
10
+ # List of predicates to be imported by `:predicates_as_macros`
11
+ # extension.
12
+ #
13
+ # @see Dry::Validation::Contract
14
+ WHITELIST = %i[
15
+ filled? gt? gteq? included_in? includes? inclusion? is? lt?
16
+ lteq? max_size? min_size? not_eql? odd? respond_to? size? true?
17
+ uuid_v4?
18
+ ].freeze
19
+
20
+ # @api private
21
+ def arg_names(name)
22
+ arg_list(name).map(&:first)
23
+ end
24
+
25
+ # @api private
26
+ def call(name, args)
27
+ self[name].(*args)
28
+ end
29
+
30
+ # @api private
31
+ def message_opts(name, arg_values)
32
+ arg_names(name).zip(arg_values).to_h
33
+ end
34
+ end
35
+
36
+ # Extension to use dry-logic predicates as macros.
37
+ #
38
+ # @see Dry::Validation::PredicateRegistry::WHITELIST Available predicates
39
+ #
40
+ # @example
41
+ # Dry::Validation.load_extensions(:predicates_as_macros)
42
+ #
43
+ # class ApplicationContract < Dry::Validation::Contract
44
+ # import_predicates_as_macros
45
+ # end
46
+ #
47
+ # class AgeContract < ApplicationContract
48
+ # schema do
49
+ # required(:age).filled(:integer)
50
+ # end
51
+ #
52
+ # rule(:age).validate(gteq?: 18)
53
+ # end
54
+ #
55
+ # AgeContract.new.(age: 17).errors.first.text
56
+ # # => 'must be greater than or equal to 18'
57
+ #
58
+ # @api public
59
+ class Contract
60
+ # Make macros available for self and its descendants.
61
+ def self.import_predicates_as_macros
62
+ registry = PredicateRegistry.new
63
+
64
+ PredicateRegistry::WHITELIST.each do |name|
65
+ register_macro(name) do |macro:|
66
+ predicate_args = [*macro.args, value]
67
+ message_opts = registry.message_opts(name, predicate_args)
68
+
69
+ key.failure(name, message_opts) unless registry.(name, predicate_args)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -45,6 +45,13 @@ module Dry
45
45
  # @example
46
46
  # failure(:taken)
47
47
  #
48
+ # @overload failure(meta_hash)
49
+ # Use meta_hash[:text] as a message (either explicitely or as an identifier),
50
+ # setting the rest of the hash as error meta attribute
51
+ # @param meta [Hash] The hash containing the message as value for the :text key
52
+ # @example
53
+ # failure({text: :invalid, key: value})
54
+ #
48
55
  # @see Evaluator#key
49
56
  # @see Evaluator#base
50
57
  #
@@ -19,21 +19,24 @@ module Dry
19
19
  # @api private
20
20
  option :block
21
21
 
22
+ # @!attribute [r] block_options
23
+ # @return [Hash]
24
+ # @api private
25
+ option :block_options, default: -> { block ? map_keywords(block) : EMPTY_HASH }
26
+
22
27
  private
23
28
 
24
29
  # Extract options for the block kwargs
25
30
  #
26
- # @return [Hash]
31
+ # @param [Proc] block Callable
32
+ # @return Hash
27
33
  #
28
34
  # @api private
29
- def block_options
30
- return EMPTY_HASH unless block
31
-
32
- @block_options ||= block
35
+ def map_keywords(block)
36
+ block
33
37
  .parameters
34
- .select { |arg| arg[0].equal?(:keyreq) }
35
- .map(&:last)
36
- .map { |name| [name, BLOCK_OPTIONS_MAPPINGS[name]] }
38
+ .select { |arg,| arg.equal?(:keyreq) }
39
+ .map { |_, name| [name, BLOCK_OPTIONS_MAPPINGS[name]] }
37
40
  .to_h
38
41
  end
39
42
  end
@@ -52,7 +52,7 @@ module Dry
52
52
  #
53
53
  # @api public
54
54
  def evaluate(**opts)
55
- evaluated_text, rest = text.(opts)
55
+ evaluated_text, rest = text.(**opts)
56
56
  Message.new(evaluated_text, path: path, meta: rest.merge(meta))
57
57
  end
58
58
  end
@@ -41,7 +41,7 @@ module Dry
41
41
  return self if new_options.empty? && other.eql?(messages)
42
42
 
43
43
  self.class.new(
44
- (other + select { |err| err.is_a?(Message) }).uniq,
44
+ other | select { |err| err.is_a?(Message) },
45
45
  options.merge(source: source_messages, **new_options)
46
46
  ).freeze
47
47
  end
@@ -54,6 +54,7 @@ module Dry
54
54
  #
55
55
  # @api private
56
56
  def add(message)
57
+ @empty = nil
57
58
  source_messages << message
58
59
  messages << message
59
60
  initialize_placeholders!
@@ -114,7 +115,7 @@ module Dry
114
115
  # rubocop:disable Metrics/AbcSize
115
116
  # rubocop:disable Metrics/PerceivedComplexity
116
117
  def initialize_placeholders!
117
- @placeholders = unique_paths.each_with_object(EMPTY_HASH.dup) { |path, hash|
118
+ @placeholders = unique_paths.sort_by(&:size).each_with_object(EMPTY_HASH.dup) { |path, hash|
118
119
  curr_idx = 0
119
120
  last_idx = path.size - 1
120
121
  node = hash
@@ -22,6 +22,8 @@ module Dry
22
22
  # Resolve Message object from provided args and path
23
23
  #
24
24
  # This is used internally by contracts when rules are applied
25
+ # If message argument is a Hash, then it MUST have a :text key,
26
+ # which value will be used as the message value
25
27
  #
26
28
  # @return [Message, Message::Localized]
27
29
  #
@@ -34,7 +36,12 @@ module Dry
34
36
  Message[message, path, meta]
35
37
  when Hash
36
38
  meta = message.dup
37
- text = meta.delete(:text)
39
+ text = meta.delete(:text) { |key|
40
+ raise ArgumentError, <<~STR
41
+ +message+ Hash must contain :#{key} key (#{message.inspect} given)
42
+ STR
43
+ }
44
+
38
45
  call(message: text, tokens: tokens, path: path, meta: meta)
39
46
  else
40
47
  raise ArgumentError, <<~STR
@@ -106,6 +106,22 @@ module Dry
106
106
  schema_result.error?(key)
107
107
  end
108
108
 
109
+ # Check if there's any error for the provided key
110
+ #
111
+ # This does not consider errors from the nested values
112
+ #
113
+ # @api private
114
+ def base_error?(key)
115
+ schema_result.errors.any? { |error|
116
+ key_path = Schema::Path[key]
117
+ err_path = Schema::Path[error.path]
118
+
119
+ return false unless key_path.same_root?(err_path)
120
+
121
+ key_path == err_path
122
+ }
123
+ end
124
+
109
125
  # Add a new error for the provided key
110
126
  #
111
127
  # @api private
@@ -82,17 +82,21 @@ module Dry
82
82
  @keys = []
83
83
 
84
84
  @block = proc do
85
- (values[root] || []).each_with_index do |_, idx|
86
- path = [*Schema::Path[root].to_a, idx]
85
+ unless result.base_error?(root) || !values.key?(root)
86
+ values[root].each_with_index do |_, idx|
87
+ path = [*Schema::Path[root].to_a, idx]
87
88
 
88
- next if result.error?(path)
89
+ next if result.error?(path)
89
90
 
90
- evaluator = with(macros: macros, keys: [path], &block)
91
+ evaluator = with(macros: macros, keys: [path], &block)
91
92
 
92
- failures.concat(evaluator.failures)
93
+ failures.concat(evaluator.failures)
94
+ end
93
95
  end
94
96
  end
95
97
 
98
+ @block_options = map_keywords(block) if block
99
+
96
100
  self
97
101
  end
98
102
 
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/schema/key'
4
+ require 'dry/schema/key_map'
5
+
6
+ module Dry
7
+ module Schema
8
+ class Path
9
+ # @api private
10
+ def multi_value?
11
+ last.is_a?(Array)
12
+ end
13
+
14
+ # @api private
15
+ def expand
16
+ to_a[0..-2].product(last).map { |spec| self.class[spec] }
17
+ end
18
+ end
19
+
20
+ # @api private
21
+ #
22
+ # TODO: this should be moved to dry-schema at some point
23
+ class Key
24
+ # @api private
25
+ def to_dot_notation
26
+ [name.to_s]
27
+ end
28
+
29
+ # @api private
30
+ class Hash < Key
31
+ # @api private
32
+ def to_dot_notation
33
+ [name].product(members.flat_map(&:to_dot_notation)).map { |e| e.join(DOT) }
34
+ end
35
+ end
36
+ end
37
+
38
+ # @api private
39
+ class KeyMap
40
+ # @api private
41
+ def to_dot_notation
42
+ @to_dot_notation ||= map(&:to_dot_notation).flatten
43
+ end
44
+ end
45
+ end
46
+ end
@@ -45,15 +45,13 @@ module Dry
45
45
 
46
46
  case (key = args[0])
47
47
  when Symbol, String, Array, Hash
48
- path = Schema::Path[key]
49
- keys = path.to_a
48
+ keys = Schema::Path[key].to_a
50
49
 
51
50
  return data.dig(*keys) unless keys.last.is_a?(Array)
52
51
 
53
52
  last = keys.pop
54
53
  vals = self.class.new(data.dig(*keys))
55
-
56
- last.map { |name| vals[name] }
54
+ vals.fetch_values(*last) { nil }
57
55
  else
58
56
  raise ArgumentError, '+key+ must be a valid path specification'
59
57
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module Validation
5
- VERSION = '1.1.1'
5
+ VERSION = '1.4.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-validation
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-24 00:00:00.000000000 Z
11
+ date: 2019-12-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -95,7 +95,7 @@ dependencies:
95
95
  version: '1.0'
96
96
  - - ">="
97
97
  - !ruby/object:Gem::Version
98
- version: 1.1.0
98
+ version: 1.3.1
99
99
  type: :runtime
100
100
  prerelease: false
101
101
  version_requirements: !ruby/object:Gem::Requirement
@@ -105,7 +105,7 @@ dependencies:
105
105
  version: '1.0'
106
106
  - - ">="
107
107
  - !ruby/object:Gem::Version
108
- version: 1.1.0
108
+ version: 1.3.1
109
109
  - !ruby/object:Gem::Dependency
110
110
  name: bundler
111
111
  requirement: !ruby/object:Gem::Requirement
@@ -168,6 +168,7 @@ files:
168
168
  - lib/dry/validation/evaluator.rb
169
169
  - lib/dry/validation/extensions/hints.rb
170
170
  - lib/dry/validation/extensions/monads.rb
171
+ - lib/dry/validation/extensions/predicates_as_macros.rb
171
172
  - lib/dry/validation/failures.rb
172
173
  - lib/dry/validation/function.rb
173
174
  - lib/dry/validation/macro.rb
@@ -177,6 +178,7 @@ files:
177
178
  - lib/dry/validation/messages/resolver.rb
178
179
  - lib/dry/validation/result.rb
179
180
  - lib/dry/validation/rule.rb
181
+ - lib/dry/validation/schema_ext.rb
180
182
  - lib/dry/validation/values.rb
181
183
  - lib/dry/validation/version.rb
182
184
  homepage: https://dry-rb.org/gems/dry-validation