dry-validation 1.2.0 → 1.5.0

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.
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2019 dry-rb team
3
+ Copyright (c) 2015-2020 dry-rb team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -1,26 +1,25 @@
1
1
  [gem]: https://rubygems.org/gems/dry-validation
2
- [travis]: https://travis-ci.com/dry-rb/dry-validation
3
- [codeclimate]: https://codeclimate.com/github/dry-rb/dry-validation
2
+ [actions]: https://github.com/dry-rb/dry-validation/actions
3
+ [codacy]: https://www.codacy.com/gh/dry-rb/dry-validation
4
4
  [chat]: https://dry-rb.zulipchat.com
5
5
  [inchpages]: http://inch-ci.org/github/dry-rb/dry-validation
6
6
 
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.com/dry-rb/dry-validation.svg?branch=master)][travis]
11
- [![Code Climate](https://codeclimate.com/github/dry-rb/dry-validation/badges/gpa.svg)][codeclimate]
12
- [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-validation/badges/coverage.svg)][codeclimate]
10
+ [![CI Status](https://github.com/dry-rb/dry-validation/workflows/ci/badge.svg)][actions]
11
+ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/f30e3ff5ec304c55a73868cdbf055c67)][codacy]
12
+ [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/f30e3ff5ec304c55a73868cdbf055c67)][codacy]
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)
17
+ * [User documentation](http://dry-rb.org/gems/dry-validation)
18
18
  * [API documentation](http://rubydoc.info/gems/dry-validation)
19
- * [Guidelines for contributing](CONTRIBUTING.md)
20
19
 
21
20
  ## Supported Ruby versions
22
21
 
23
- This library officially supports following Ruby versions:
22
+ This library officially supports the following Ruby versions:
24
23
 
25
24
  * MRI >= `2.4`
26
25
  * jruby >= `9.2`
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ # this file is managed by dry-rb/devtools project
3
+
4
+ lib = File.expand_path('lib', __dir__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require 'dry/validation/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'dry-validation'
10
+ spec.authors = ["Piotr Solnica"]
11
+ spec.email = ["piotr.solnica@gmail.com"]
12
+ spec.license = 'MIT'
13
+ spec.version = Dry::Validation::VERSION.dup
14
+
15
+ spec.summary = "Validation library"
16
+ spec.description = spec.summary
17
+ spec.homepage = 'https://dry-rb.org/gems/dry-validation'
18
+ spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-validation.gemspec", "lib/**/*", "config/*.yml"]
19
+ spec.bindir = 'bin'
20
+ spec.executables = []
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
24
+ spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-validation/blob/master/CHANGELOG.md'
25
+ spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-validation'
26
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-validation/issues'
27
+
28
+ spec.required_ruby_version = ">= 2.4.0"
29
+
30
+ # to update dependencies edit project.yml
31
+ spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
32
+ spec.add_runtime_dependency "dry-container", "~> 0.7", ">= 0.7.1"
33
+ spec.add_runtime_dependency "dry-core", "~> 0.4"
34
+ spec.add_runtime_dependency "dry-equalizer", "~> 0.2"
35
+ spec.add_runtime_dependency "dry-initializer", "~> 3.0"
36
+ spec.add_runtime_dependency "dry-schema", "~> 1.5"
37
+
38
+ spec.add_development_dependency "bundler"
39
+ spec.add_development_dependency "rake"
40
+ spec.add_development_dependency "rspec"
41
+ end
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/validation'
3
+ require "dry/validation"
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/validation/constants'
4
- require 'dry/validation/contract'
5
- require 'dry/validation/macros'
3
+ require "dry/validation/constants"
4
+ require "dry/validation/contract"
5
+ require "dry/validation/macros"
6
6
 
7
7
  # Main namespace
8
8
  #
@@ -16,15 +16,15 @@ module Dry
16
16
  extend Macros::Registrar
17
17
 
18
18
  register_extension(:monads) do
19
- require 'dry/validation/extensions/monads'
19
+ require "dry/validation/extensions/monads"
20
20
  end
21
21
 
22
22
  register_extension(:hints) do
23
- require 'dry/validation/extensions/hints'
23
+ require "dry/validation/extensions/hints"
24
24
  end
25
25
 
26
26
  register_extension(:predicates_as_macros) do
27
- require 'dry/validation/extensions/predicates_as_macros'
27
+ require "dry/validation/extensions/predicates_as_macros"
28
28
  end
29
29
 
30
30
  # Define a contract and build its instance
@@ -46,11 +46,9 @@ module Dry
46
46
  #
47
47
  # @api public
48
48
  #
49
- # rubocop:disable Naming/MethodName
50
49
  def self.Contract(options = EMPTY_HASH, &block)
51
50
  Contract.build(options, &block)
52
51
  end
53
- # rubocop:enable Naming/MethodName
54
52
 
55
53
  # This is needed by Macros::Registrar
56
54
  #
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema/config'
4
- require 'dry/validation/macros'
3
+ require "dry/schema/config"
4
+ require "dry/validation/macros"
5
5
 
6
6
  module Dry
7
7
  module Validation
@@ -1,21 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core/constants'
3
+ require "pathname"
4
+ require "dry/core/constants"
4
5
 
5
6
  module Dry
6
7
  module Validation
7
8
  include Dry::Core::Constants
8
9
 
9
- DOT = '.'
10
+ DOT = "."
10
11
 
11
12
  # Root path is used for base errors in hash representation of error messages
12
13
  ROOT_PATH = [nil].freeze
13
14
 
14
15
  # Path to the default errors locale file
15
- DEFAULT_ERRORS_NAMESPACE = 'dry_validation'
16
+ DEFAULT_ERRORS_NAMESPACE = "dry_validation"
16
17
 
17
18
  # Path to the default errors locale file
18
- DEFAULT_ERRORS_PATH = Pathname(__FILE__).join('../../../../config/errors.yml').realpath.freeze
19
+ DEFAULT_ERRORS_PATH = Pathname(__FILE__).join("../../../../config/errors.yml").realpath.freeze
19
20
 
20
21
  # Mapping for block kwarg options used by block_options
21
22
  #
@@ -1,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/map'
3
+ require "concurrent/map"
4
4
 
5
- require 'dry/equalizer'
6
- require 'dry/initializer'
7
- require 'dry/schema/path'
5
+ require "dry/equalizer"
6
+ require "dry/initializer"
7
+ require "dry/schema/path"
8
8
 
9
- require 'dry/validation/config'
10
- require 'dry/validation/constants'
11
- require 'dry/validation/rule'
12
- require 'dry/validation/evaluator'
13
- require 'dry/validation/messages/resolver'
14
- require 'dry/validation/result'
15
- require 'dry/validation/contract/class_interface'
9
+ require "dry/validation/config"
10
+ require "dry/validation/constants"
11
+ require "dry/validation/rule"
12
+ require "dry/validation/evaluator"
13
+ require "dry/validation/messages/resolver"
14
+ require "dry/validation/result"
15
+ require "dry/validation/contract/class_interface"
16
16
 
17
17
  module Dry
18
18
  module Validation
@@ -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
@@ -119,6 +119,10 @@ module Dry
119
119
  def error?(result, spec)
120
120
  path = Schema::Path[spec]
121
121
 
122
+ if path.multi_value?
123
+ return path.expand.any? { |nested_path| error?(result, nested_path) }
124
+ end
125
+
122
126
  return true if result.error?(path)
123
127
 
124
128
  path
@@ -128,7 +132,9 @@ module Dry
128
132
 
129
133
  return false unless result.error?(curr_path)
130
134
 
131
- result.errors.any? { |err| Schema::Path[err.path] == curr_path }
135
+ result.errors.any? { |err|
136
+ (other = Schema::Path[err.path]).same_root?(curr_path) && other == curr_path
137
+ }
132
138
  }
133
139
  end
134
140
 
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/schema'
4
- require 'dry/schema/messages'
5
- require 'dry/schema/path'
6
- require 'dry/schema/key_map'
3
+ require "dry/schema"
4
+ require "dry/schema/messages"
5
+ require "dry/schema/path"
6
+ require "dry/schema/key_map"
7
7
 
8
- require 'dry/validation/constants'
9
- require 'dry/validation/macros'
10
- require 'dry/validation/schema_ext'
8
+ require "dry/validation/constants"
9
+ require "dry/validation/macros"
10
+ require "dry/validation/schema_ext"
11
11
 
12
12
  module Dry
13
13
  module Validation
@@ -23,7 +23,7 @@ module Dry
23
23
  # @api private
24
24
  def inherited(klass)
25
25
  super
26
- klass.instance_variable_set('@config', config.dup)
26
+ klass.instance_variable_set("@config", config.dup)
27
27
  end
28
28
 
29
29
  # Configuration
@@ -53,36 +53,36 @@ module Dry
53
53
  #
54
54
  # This type of schema is suitable for HTTP parameters
55
55
  #
56
- # @return [Dry::Schema::Params]
56
+ # @return [Dry::Schema::Params,NilClass]
57
57
  # @see https://dry-rb.org/gems/dry-schema/params/
58
58
  #
59
59
  # @api public
60
- def params(&block)
61
- define(:Params, &block)
60
+ def params(*external_schemas, &block)
61
+ define(:Params, external_schemas, &block)
62
62
  end
63
63
 
64
64
  # Define a JSON schema for your contract
65
65
  #
66
66
  # This type of schema is suitable for JSON data
67
67
  #
68
- # @return [Dry::Schema::JSON]
68
+ # @return [Dry::Schema::JSON,NilClass]
69
69
  # @see https://dry-rb.org/gems/dry-schema/json/
70
70
  #
71
71
  # @api public
72
- def json(&block)
73
- define(:JSON, &block)
72
+ def json(*external_schemas, &block)
73
+ define(:JSON, external_schemas, &block)
74
74
  end
75
75
 
76
76
  # Define a plain schema for your contract
77
77
  #
78
78
  # This type of schema does not offer coercion out of the box
79
79
  #
80
- # @return [Dry::Schema::Processor]
80
+ # @return [Dry::Schema::Processor,NilClass]
81
81
  # @see https://dry-rb.org/gems/dry-schema/
82
82
  #
83
83
  # @api public
84
- def schema(&block)
85
- define(:schema, &block)
84
+ def schema(*external_schemas, &block)
85
+ define(:schema, external_schemas, &block)
86
86
  end
87
87
 
88
88
  # Define a rule for your contract
@@ -101,7 +101,7 @@ module Dry
101
101
  #
102
102
  # @api public
103
103
  def rule(*keys, &block)
104
- ensure_valid_keys(*keys)
104
+ ensure_valid_keys(*keys) if __schema__
105
105
 
106
106
  Rule.new(keys: keys, block: block).tap do |rule|
107
107
  rules << rule
@@ -123,7 +123,7 @@ module Dry
123
123
  #
124
124
  # @api public
125
125
  def build(options = EMPTY_HASH, &block)
126
- Class.new(self, &block).new(options)
126
+ Class.new(self, &block).new(**options)
127
127
  end
128
128
 
129
129
  # @api private
@@ -154,29 +154,15 @@ module Dry
154
154
  private
155
155
 
156
156
  # @api private
157
- # rubocop:disable Metrics/AbcSize
158
157
  def ensure_valid_keys(*keys)
159
- valid_paths = key_map.to_dot_notation.map { |value| Schema::Path[value] }
160
-
161
- invalid_keys = keys
162
- .map { |key|
163
- [key, Schema::Path[key]]
164
- }
165
- .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
- .flatten(1)
176
- .reject { |(_, path)|
177
- valid_paths.any? { |valid_path| valid_path.include?(path) }
178
- }
179
- .map(&:first)
158
+ valid_paths = key_map.to_dot_notation
159
+ key_paths = key_paths(keys)
160
+
161
+ invalid_keys = key_paths.map { |(key, path)|
162
+ unless valid_paths.any? { |vp| vp.include?(path) || vp.include?("#{path}[]") }
163
+ key
164
+ end
165
+ }.compact.uniq
180
166
 
181
167
  return if invalid_keys.empty?
182
168
 
@@ -184,7 +170,28 @@ module Dry
184
170
  #{name}.rule specifies keys that are not defined by the schema: #{invalid_keys.inspect}
185
171
  STR
186
172
  end
187
- # rubocop:enable Metrics/AbcSize
173
+
174
+ # @api private
175
+ def key_paths(keys)
176
+ keys.map { |key|
177
+ case key
178
+ when Hash
179
+ path = Schema::Path[key]
180
+ if path.multi_value?
181
+ *head, tail = Array(path)
182
+ [key].product(
183
+ tail.map { |el| [*head, *el] }.map { |parts| parts.join(DOT) }
184
+ )
185
+ else
186
+ [[key, path.to_a.join(DOT)]]
187
+ end
188
+ when Array
189
+ [[key, Schema::Path[key].to_a.join(DOT)]]
190
+ else
191
+ [[key, key.to_s]]
192
+ end
193
+ }.flatten(1)
194
+ end
188
195
 
189
196
  # @api private
190
197
  def key_map
@@ -192,23 +199,29 @@ module Dry
192
199
  end
193
200
 
194
201
  # @api private
195
- def schema_opts
196
- { parent: superclass&.__schema__, config: config }
202
+ def core_schema_opts
203
+ {parent: superclass&.__schema__, config: config}
197
204
  end
198
205
 
199
206
  # @api private
200
- def define(method_name, &block)
201
- if defined?(@__schema__)
202
- raise ::Dry::Validation::DuplicateSchemaError, 'Schema has already been defined'
207
+ def define(method_name, external_schemas, &block)
208
+ return __schema__ if external_schemas.empty? && block.nil?
209
+
210
+ unless __schema__.nil?
211
+ raise ::Dry::Validation::DuplicateSchemaError, "Schema has already been defined"
203
212
  end
204
213
 
214
+ schema_opts = core_schema_opts
215
+
216
+ schema_opts.update(parent: external_schemas) if external_schemas.any?
217
+
205
218
  case method_name
206
219
  when :schema
207
- @__schema__ = Schema.define(schema_opts, &block)
220
+ @__schema__ = Schema.define(**schema_opts, &block)
208
221
  when :Params
209
- @__schema__ = Schema.Params(schema_opts, &block)
222
+ @__schema__ = Schema.Params(**schema_opts, &block)
210
223
  when :JSON
211
- @__schema__ = Schema.JSON(schema_opts, &block)
224
+ @__schema__ = Schema.JSON(**schema_opts, &block)
212
225
  end
213
226
  end
214
227
  end
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/initializer'
3
+ require "dry/initializer"
4
+ require "dry/core/deprecations"
4
5
 
5
- require 'dry/validation/constants'
6
- require 'dry/validation/failures'
6
+ require "dry/validation/constants"
7
+ require "dry/validation/failures"
7
8
 
8
9
  module Dry
9
10
  module Validation
@@ -16,6 +17,9 @@ module Dry
16
17
  # @api public
17
18
  class Evaluator
18
19
  extend Dry::Initializer
20
+ extend Dry::Core::Deprecations[:'dry-validation']
21
+
22
+ deprecate :error?, :schema_error?
19
23
 
20
24
  # @!attribute [r] _contract
21
25
  # @return [Contract]
@@ -63,19 +67,19 @@ module Dry
63
67
  # Initialize a new evaluator
64
68
  #
65
69
  # @api private
66
- def initialize(contract, options, &block)
67
- super(contract, options)
70
+ def initialize(contract, **options, &block)
71
+ super(contract, **options)
68
72
 
69
73
  @_options = options
70
74
 
71
75
  if block
72
76
  exec_opts = block_options.map { |key, value| [key, _options[value]] }.to_h
73
- instance_exec(exec_opts, &block)
77
+ instance_exec(**exec_opts, &block)
74
78
  end
75
79
 
76
80
  macros.each do |args|
77
81
  macro = macro(*args.flatten(1))
78
- instance_exec(macro.extract_block_options(_options.merge(macro: macro)), &macro.block)
82
+ instance_exec(**macro.extract_block_options(_options.merge(macro: macro)), &macro.block)
79
83
  end
80
84
  end
81
85
 
@@ -117,7 +121,7 @@ module Dry
117
121
 
118
122
  # @api private
119
123
  def with(new_opts, &block)
120
- self.class.new(_contract, _options.merge(new_opts), &block)
124
+ self.class.new(_contract, **_options, **new_opts, &block)
121
125
  end
122
126
 
123
127
  # Return default (first) key name
@@ -163,17 +167,26 @@ module Dry
163
167
  values.key?(key_name)
164
168
  end
165
169
 
166
- # Check if there are any errors under the provided path
170
+ # Check if there are any errors on the schema under the provided path
167
171
  #
168
172
  # @param [Symbol, String, Array] A Path-compatible spec
169
173
  #
170
174
  # @return [Boolean]
171
175
  #
172
176
  # @api public
173
- def error?(path)
177
+ def schema_error?(path)
174
178
  result.error?(path)
175
179
  end
176
180
 
181
+ # Check if there are any errors on the current rule
182
+ #
183
+ # @return [Boolean]
184
+ #
185
+ # @api public
186
+ def rule_error?
187
+ !key(path).empty?
188
+ end
189
+
177
190
  # @api private
178
191
  def respond_to_missing?(meth, include_private = false)
179
192
  super || _contract.respond_to?(meth, true)
@@ -192,6 +205,7 @@ module Dry
192
205
  super
193
206
  end
194
207
  end
208
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
195
209
  end
196
210
  end
197
211
  end