dry-validation 1.3.1 → 1.8.1
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 +378 -164
- data/LICENSE +1 -1
- data/README.md +10 -10
- data/dry-validation.gemspec +41 -0
- data/lib/dry/validation/config.rb +3 -3
- data/lib/dry/validation/constants.rb +5 -5
- data/lib/dry/validation/contract/class_interface.rb +55 -46
- data/lib/dry/validation/contract.rb +29 -16
- data/lib/dry/validation/evaluator.rb +53 -17
- data/lib/dry/validation/extensions/hints.rb +1 -3
- data/lib/dry/validation/extensions/monads.rb +1 -1
- data/lib/dry/validation/extensions/predicates_as_macros.rb +3 -3
- data/lib/dry/validation/failures.rb +15 -3
- data/lib/dry/validation/function.rb +3 -4
- data/lib/dry/validation/macro.rb +3 -3
- data/lib/dry/validation/macros.rb +3 -3
- data/lib/dry/validation/message.rb +6 -4
- data/lib/dry/validation/message_set.rb +6 -51
- data/lib/dry/validation/messages/resolver.rb +56 -6
- data/lib/dry/validation/result.rb +44 -7
- data/lib/dry/validation/rule.rb +18 -9
- data/lib/dry/validation/schema_ext.rb +1 -28
- data/lib/dry/validation/values.rb +17 -6
- data/lib/dry/validation/version.rb +1 -1
- data/lib/dry/validation.rb +6 -8
- data/lib/dry-validation.rb +1 -1
- metadata +31 -34
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "dry/container"
|
4
|
+
require "dry/validation/macro"
|
5
5
|
|
6
6
|
module Dry
|
7
7
|
module Validation
|
@@ -25,7 +25,7 @@ module Dry
|
|
25
25
|
# end
|
26
26
|
#
|
27
27
|
# @param [Symbol] name The name of the macro
|
28
|
-
# @param [Array]
|
28
|
+
# @param [Array] args Optional default positional arguments for the macro
|
29
29
|
#
|
30
30
|
# @return [self]
|
31
31
|
#
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "dry/core/equalizer"
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
5
|
+
require "dry/schema/constants"
|
6
|
+
require "dry/schema/message"
|
7
7
|
|
8
8
|
module Dry
|
9
9
|
module Validation
|
@@ -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
|
@@ -70,11 +70,13 @@ module Dry
|
|
70
70
|
# Initialize a new error object
|
71
71
|
#
|
72
72
|
# @api private
|
73
|
+
# rubocop: disable Lint/MissingSuper
|
73
74
|
def initialize(text, path:, meta: EMPTY_HASH)
|
74
75
|
@text = text
|
75
76
|
@path = Array(path)
|
76
77
|
@meta = meta
|
77
78
|
end
|
79
|
+
# rubocop: enable Lint/MissingSuper
|
78
80
|
|
79
81
|
# Check if this is a base error not associated with any key
|
80
82
|
#
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "dry/schema/message_set"
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
5
|
+
require "dry/validation/constants"
|
6
|
+
require "dry/validation/message"
|
7
7
|
|
8
8
|
module Dry
|
9
9
|
module Validation
|
@@ -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
|
-
|
44
|
+
other | select { |err| err.is_a?(Message) },
|
45
45
|
options.merge(source: source_messages, **new_options)
|
46
46
|
).freeze
|
47
47
|
end
|
@@ -54,9 +54,9 @@ 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
|
-
initialize_placeholders!
|
60
60
|
self
|
61
61
|
end
|
62
62
|
|
@@ -85,58 +85,13 @@ module Dry
|
|
85
85
|
# @api private
|
86
86
|
def freeze
|
87
87
|
source_messages.select { |err| err.respond_to?(:evaluate) }.each do |err|
|
88
|
-
idx = source_messages.index(err)
|
88
|
+
idx = messages.index(err) || source_messages.index(err)
|
89
89
|
msg = err.evaluate(locale: locale, full: options[:full])
|
90
90
|
messages[idx] = msg
|
91
91
|
end
|
92
92
|
to_h
|
93
93
|
self
|
94
94
|
end
|
95
|
-
|
96
|
-
private
|
97
|
-
|
98
|
-
# @api private
|
99
|
-
def unique_paths
|
100
|
-
source_messages.uniq(&:path).map(&:path)
|
101
|
-
end
|
102
|
-
|
103
|
-
# @api private
|
104
|
-
def messages_map
|
105
|
-
@messages_map ||= reduce(placeholders) { |hash, msg|
|
106
|
-
node = msg.path.reduce(hash) { |a, e| a.is_a?(Hash) ? a[e] : a.last[e] }
|
107
|
-
(node[0].is_a?(::Array) ? node[0] : node) << msg.dump
|
108
|
-
hash
|
109
|
-
}
|
110
|
-
end
|
111
|
-
|
112
|
-
# @api private
|
113
|
-
#
|
114
|
-
# rubocop:disable Metrics/AbcSize
|
115
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
116
|
-
def initialize_placeholders!
|
117
|
-
@placeholders = unique_paths.each_with_object(EMPTY_HASH.dup) { |path, hash|
|
118
|
-
curr_idx = 0
|
119
|
-
last_idx = path.size - 1
|
120
|
-
node = hash
|
121
|
-
|
122
|
-
while curr_idx <= last_idx
|
123
|
-
key = path[curr_idx]
|
124
|
-
|
125
|
-
next_node =
|
126
|
-
if node.is_a?(Array) && key.is_a?(Symbol)
|
127
|
-
node_hash = (node << [] << {}).last
|
128
|
-
node_hash[key] || (node_hash[key] = curr_idx < last_idx ? {} : [])
|
129
|
-
else
|
130
|
-
node[key] || (node[key] = curr_idx < last_idx ? {} : [])
|
131
|
-
end
|
132
|
-
|
133
|
-
node = next_node
|
134
|
-
curr_idx += 1
|
135
|
-
end
|
136
|
-
}
|
137
|
-
end
|
138
|
-
# rubocop:enable Metrics/AbcSize
|
139
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
140
95
|
end
|
141
96
|
end
|
142
97
|
end
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "dry/validation/message"
|
4
|
+
require "dry/schema/message_compiler"
|
4
5
|
|
5
6
|
module Dry
|
6
7
|
module Validation
|
7
8
|
module Messages
|
9
|
+
FULL_MESSAGE_WHITESPACE = Dry::Schema::MessageCompiler::FULL_MESSAGE_WHITESPACE
|
10
|
+
|
8
11
|
# Resolve translated messages from failure arguments
|
9
12
|
#
|
10
13
|
# @api public
|
@@ -22,6 +25,8 @@ module Dry
|
|
22
25
|
# Resolve Message object from provided args and path
|
23
26
|
#
|
24
27
|
# This is used internally by contracts when rules are applied
|
28
|
+
# If message argument is a Hash, then it MUST have a :text key,
|
29
|
+
# which value will be used as the message value
|
25
30
|
#
|
26
31
|
# @return [Message, Message::Localized]
|
27
32
|
#
|
@@ -31,10 +36,15 @@ module Dry
|
|
31
36
|
when Symbol
|
32
37
|
Message[->(**opts) { message(message, path: path, tokens: tokens, **opts) }, path, meta]
|
33
38
|
when String
|
34
|
-
Message[message, path, meta]
|
39
|
+
Message[->(**opts) { [message_text(message, path: path, **opts), meta] }, path, meta]
|
35
40
|
when Hash
|
36
41
|
meta = message.dup
|
37
|
-
text = meta.delete(:text)
|
42
|
+
text = meta.delete(:text) { |key|
|
43
|
+
raise ArgumentError, <<~STR
|
44
|
+
+message+ Hash must contain :#{key} key (#{message.inspect} given)
|
45
|
+
STR
|
46
|
+
}
|
47
|
+
|
38
48
|
call(message: text, tokens: tokens, path: path, meta: meta)
|
39
49
|
else
|
40
50
|
raise ArgumentError, <<~STR
|
@@ -51,7 +61,8 @@ module Dry
|
|
51
61
|
# @api public
|
52
62
|
#
|
53
63
|
# rubocop:disable Metrics/AbcSize
|
54
|
-
|
64
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
65
|
+
def message(rule, path:, tokens: EMPTY_HASH, locale: nil, full: false)
|
55
66
|
keys = path.to_a.compact
|
56
67
|
msg_opts = tokens.merge(path: keys, locale: locale || messages.default_locale)
|
57
68
|
|
@@ -62,17 +73,56 @@ module Dry
|
|
62
73
|
template, meta = messages[rule, msg_opts.merge(path: keys.last)] unless template
|
63
74
|
end
|
64
75
|
|
76
|
+
if !template && keys.size > 1
|
77
|
+
non_index_keys = keys.reject { |k| k.is_a?(Integer) }
|
78
|
+
template, meta = messages[rule, msg_opts.merge(path: non_index_keys.join(DOT))]
|
79
|
+
end
|
80
|
+
|
65
81
|
unless template
|
66
82
|
raise MissingMessageError, <<~STR
|
67
83
|
Message template for #{rule.inspect} under #{keys.join(DOT).inspect} was not found
|
68
84
|
STR
|
69
85
|
end
|
70
86
|
|
71
|
-
|
87
|
+
parsed_tokens = parse_tokens(tokens)
|
88
|
+
text = template.(template.data(parsed_tokens))
|
72
89
|
|
73
|
-
[
|
90
|
+
[message_text(text, path: path, locale: locale, full: full), meta]
|
74
91
|
end
|
92
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
75
93
|
# rubocop:enable Metrics/AbcSize
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def message_text(text, path:, locale: nil, full: false)
|
98
|
+
return text unless full
|
99
|
+
|
100
|
+
key = key_text(path: path, locale: locale)
|
101
|
+
|
102
|
+
[key, text].compact.join(FULL_MESSAGE_WHITESPACE[locale])
|
103
|
+
end
|
104
|
+
|
105
|
+
def key_text(path:, locale: nil)
|
106
|
+
locale ||= messages.default_locale
|
107
|
+
|
108
|
+
keys = path.to_a.compact
|
109
|
+
msg_opts = {path: keys, locale: locale}
|
110
|
+
|
111
|
+
messages.rule(keys.last, msg_opts) || keys.last
|
112
|
+
end
|
113
|
+
|
114
|
+
def parse_tokens(tokens)
|
115
|
+
tokens.transform_values { parse_token(_1) }
|
116
|
+
end
|
117
|
+
|
118
|
+
def parse_token(token)
|
119
|
+
case token
|
120
|
+
when Array
|
121
|
+
token.join(", ")
|
122
|
+
else
|
123
|
+
token
|
124
|
+
end
|
125
|
+
end
|
76
126
|
end
|
77
127
|
end
|
78
128
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "concurrent/map"
|
4
|
+
require "dry/core/equalizer"
|
5
5
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
6
|
+
require "dry/validation/constants"
|
7
|
+
require "dry/validation/message_set"
|
8
|
+
require "dry/validation/values"
|
9
9
|
|
10
10
|
module Dry
|
11
11
|
module Validation
|
@@ -101,11 +101,32 @@ module Dry
|
|
101
101
|
|
102
102
|
# Check if values include an error for the provided key
|
103
103
|
#
|
104
|
-
# @api
|
104
|
+
# @api public
|
105
105
|
def error?(key)
|
106
|
+
errors.any? { |msg| Schema::Path[msg.path].include?(Schema::Path[key]) }
|
107
|
+
end
|
108
|
+
|
109
|
+
# Check if the base schema (without rules) includes an error for the provided key
|
110
|
+
#
|
111
|
+
# @api private
|
112
|
+
def schema_error?(key)
|
106
113
|
schema_result.error?(key)
|
107
114
|
end
|
108
115
|
|
116
|
+
# Check if the rules includes an error for the provided key
|
117
|
+
#
|
118
|
+
# @api private
|
119
|
+
def rule_error?(key)
|
120
|
+
!schema_error?(key) && error?(key)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Check if the result contains any base rule errors
|
124
|
+
#
|
125
|
+
# @api private
|
126
|
+
def base_rule_error?
|
127
|
+
!errors.filter(:base?).empty?
|
128
|
+
end
|
129
|
+
|
109
130
|
# Check if there's any error for the provided key
|
110
131
|
#
|
111
132
|
# This does not consider errors from the nested values
|
@@ -116,7 +137,7 @@ module Dry
|
|
116
137
|
key_path = Schema::Path[key]
|
117
138
|
err_path = Schema::Path[error.path]
|
118
139
|
|
119
|
-
|
140
|
+
next unless key_path.same_root?(err_path)
|
120
141
|
|
121
142
|
key_path == err_path
|
122
143
|
}
|
@@ -179,6 +200,22 @@ module Dry
|
|
179
200
|
super
|
180
201
|
end
|
181
202
|
|
203
|
+
if RUBY_VERSION >= "2.7"
|
204
|
+
# Pattern matching
|
205
|
+
#
|
206
|
+
# @api private
|
207
|
+
def deconstruct_keys(keys)
|
208
|
+
values.deconstruct_keys(keys)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Pattern matching
|
212
|
+
#
|
213
|
+
# @api private
|
214
|
+
def deconstruct
|
215
|
+
[values, context.each.to_h]
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
182
219
|
private
|
183
220
|
|
184
221
|
# @api private
|
data/lib/dry/validation/rule.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "dry/core/equalizer"
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
5
|
+
require "dry/validation/constants"
|
6
|
+
require "dry/validation/function"
|
7
7
|
|
8
8
|
module Dry
|
9
9
|
module Validation
|
@@ -65,8 +65,8 @@ module Dry
|
|
65
65
|
# for a given array item.
|
66
66
|
#
|
67
67
|
# @example
|
68
|
-
# rule(:nums).each do
|
69
|
-
# key.failure("must be greater than 0") if value < 0
|
68
|
+
# rule(:nums).each do |index:|
|
69
|
+
# key([:number, index]).failure("must be greater than 0") if value < 0
|
70
70
|
# end
|
71
71
|
# rule(:nums).each(min: 3)
|
72
72
|
# rule(address: :city) do
|
@@ -76,19 +76,21 @@ module Dry
|
|
76
76
|
# @return [Rule]
|
77
77
|
#
|
78
78
|
# @api public
|
79
|
+
#
|
80
|
+
# rubocop:disable Metrics/AbcSize
|
79
81
|
def each(*macros, &block)
|
80
82
|
root = keys[0]
|
81
83
|
macros = parse_macros(*macros)
|
82
84
|
@keys = []
|
83
85
|
|
84
86
|
@block = proc do
|
85
|
-
unless result.base_error?(root) || !values.key?(root)
|
87
|
+
unless result.base_error?(root) || !values.key?(root) || values[root].nil?
|
86
88
|
values[root].each_with_index do |_, idx|
|
87
89
|
path = [*Schema::Path[root].to_a, idx]
|
88
90
|
|
89
|
-
next if result.
|
91
|
+
next if result.schema_error?(path)
|
90
92
|
|
91
|
-
evaluator = with(macros: macros, keys: [path], &block)
|
93
|
+
evaluator = with(macros: macros, keys: [path], index: idx, &block)
|
92
94
|
|
93
95
|
failures.concat(evaluator.failures)
|
94
96
|
end
|
@@ -99,6 +101,7 @@ module Dry
|
|
99
101
|
|
100
102
|
self
|
101
103
|
end
|
104
|
+
# rubocop:enable Metrics/AbcSize
|
102
105
|
|
103
106
|
# Return a nice string representation
|
104
107
|
#
|
@@ -118,12 +121,18 @@ module Dry
|
|
118
121
|
args.each_with_object([]) do |spec, macros|
|
119
122
|
case spec
|
120
123
|
when Hash
|
121
|
-
|
124
|
+
add_macro_from_hash(macros, spec)
|
122
125
|
else
|
123
126
|
macros << Array(spec)
|
124
127
|
end
|
125
128
|
end
|
126
129
|
end
|
130
|
+
|
131
|
+
def add_macro_from_hash(macros, spec)
|
132
|
+
spec.each do |k, v|
|
133
|
+
macros << [k, v.is_a?(Array) ? v : [v]]
|
134
|
+
end
|
135
|
+
end
|
127
136
|
end
|
128
137
|
end
|
129
138
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require 'dry/schema/key_map'
|
3
|
+
require "dry/schema/path"
|
5
4
|
|
6
5
|
module Dry
|
7
6
|
module Schema
|
@@ -16,31 +15,5 @@ module Dry
|
|
16
15
|
to_a[0..-2].product(last).map { |spec| self.class[spec] }
|
17
16
|
end
|
18
17
|
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.map(&:to_dot_notation).flatten(1)).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
18
|
end
|
46
19
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "dry/core/equalizer"
|
4
|
+
require "dry/schema/path"
|
5
|
+
require "dry/validation/constants"
|
6
6
|
|
7
7
|
module Dry
|
8
8
|
module Validation
|
@@ -35,7 +35,10 @@ module Dry
|
|
35
35
|
# key.failure('must be > 18') if values[:age] <= 18
|
36
36
|
# end
|
37
37
|
#
|
38
|
-
# @param [Symbol]
|
38
|
+
# @param args [Symbol, String, Hash, Array<Symbol>] If given as a single
|
39
|
+
# Symbol, String, Array or Hash, build a key array using
|
40
|
+
# {Dry::Schema::Path} digging for data. If given as positional
|
41
|
+
# arguments, use these with Hash#dig on the data directly.
|
39
42
|
#
|
40
43
|
# @return [Object]
|
41
44
|
#
|
@@ -53,11 +56,12 @@ module Dry
|
|
53
56
|
vals = self.class.new(data.dig(*keys))
|
54
57
|
vals.fetch_values(*last) { nil }
|
55
58
|
else
|
56
|
-
raise ArgumentError,
|
59
|
+
raise ArgumentError, "+key+ must be a valid path specification"
|
57
60
|
end
|
58
61
|
end
|
59
62
|
|
60
63
|
# @api public
|
64
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
61
65
|
def key?(key, hash = data)
|
62
66
|
return hash.key?(key) if key.is_a?(Symbol)
|
63
67
|
|
@@ -65,14 +69,20 @@ module Dry
|
|
65
69
|
if e.is_a?(Array)
|
66
70
|
result = e.all? { |k| key?(k, a) }
|
67
71
|
return result
|
72
|
+
elsif e.is_a?(Symbol) && a.is_a?(Array)
|
73
|
+
return false
|
74
|
+
elsif a.nil?
|
75
|
+
return false
|
76
|
+
elsif a.is_a?(String)
|
77
|
+
return false
|
68
78
|
else
|
69
79
|
return false unless a.is_a?(Array) ? (e >= 0 && e < a.size) : a.key?(e)
|
70
80
|
end
|
71
81
|
a[e]
|
72
82
|
end
|
73
|
-
|
74
83
|
true
|
75
84
|
end
|
85
|
+
# rubocop: enable Metrics/PerceivedComplexity
|
76
86
|
|
77
87
|
# @api private
|
78
88
|
def respond_to_missing?(meth, include_private = false)
|
@@ -89,6 +99,7 @@ module Dry
|
|
89
99
|
super
|
90
100
|
end
|
91
101
|
end
|
102
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
92
103
|
end
|
93
104
|
end
|
94
105
|
end
|
data/lib/dry/validation.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
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
|
19
|
+
require "dry/validation/extensions/monads"
|
20
20
|
end
|
21
21
|
|
22
22
|
register_extension(:hints) do
|
23
|
-
require
|
23
|
+
require "dry/validation/extensions/hints"
|
24
24
|
end
|
25
25
|
|
26
26
|
register_extension(:predicates_as_macros) do
|
27
|
-
require
|
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
|
#
|
data/lib/dry-validation.rb
CHANGED