dry-validation 0.13.3 → 1.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +506 -95
- data/LICENSE +1 -1
- data/README.md +16 -12
- data/config/errors.yml +3 -88
- data/dry-validation.gemspec +37 -24
- data/lib/dry/validation/config.rb +24 -0
- data/lib/dry/validation/constants.rb +43 -0
- data/lib/dry/validation/contract/class_interface.rb +230 -0
- data/lib/dry/validation/contract.rb +171 -0
- data/lib/dry/validation/evaluator.rb +224 -0
- data/lib/dry/validation/extensions/hints.rb +67 -0
- data/lib/dry/validation/extensions/monads.rb +24 -8
- data/lib/dry/validation/extensions/predicates_as_macros.rb +75 -0
- data/lib/dry/validation/failures.rb +70 -0
- data/lib/dry/validation/function.rb +44 -0
- data/lib/dry/validation/macro.rb +38 -0
- data/lib/dry/validation/macros.rb +104 -0
- data/lib/dry/validation/message.rb +80 -80
- data/lib/dry/validation/message_set.rb +80 -105
- data/lib/dry/validation/messages/resolver.rb +131 -0
- data/lib/dry/validation/result.rb +183 -41
- data/lib/dry/validation/rule.rb +135 -0
- data/lib/dry/validation/schema_ext.rb +19 -0
- data/lib/dry/validation/values.rb +104 -0
- data/lib/dry/validation/version.rb +3 -1
- data/lib/dry/validation.rb +45 -28
- data/lib/dry-validation.rb +3 -1
- metadata +46 -344
- data/.codeclimate.yml +0 -17
- data/.gitignore +0 -9
- data/.rspec +0 -3
- data/.travis.yml +0 -29
- data/CONTRIBUTING.md +0 -31
- data/Gemfile +0 -25
- data/Rakefile +0 -22
- data/benchmarks/benchmark_form_invalid.rb +0 -64
- data/benchmarks/benchmark_form_valid.rb +0 -64
- data/benchmarks/benchmark_schema_invalid_huge.rb +0 -52
- data/benchmarks/profile_schema_call_invalid.rb +0 -20
- data/benchmarks/profile_schema_call_valid.rb +0 -20
- data/benchmarks/profile_schema_definition.rb +0 -14
- data/benchmarks/profile_schema_huge_invalid.rb +0 -30
- data/benchmarks/profile_schema_messages_invalid.rb +0 -20
- data/benchmarks/suite.rb +0 -5
- data/examples/basic.rb +0 -15
- data/examples/each.rb +0 -14
- data/examples/json.rb +0 -12
- data/examples/multiple.rb +0 -27
- data/examples/nested.rb +0 -22
- data/examples/params.rb +0 -11
- data/lib/dry/validation/compat/form.rb +0 -67
- data/lib/dry/validation/deprecations.rb +0 -24
- data/lib/dry/validation/executor.rb +0 -91
- data/lib/dry/validation/extensions/struct.rb +0 -32
- data/lib/dry/validation/extensions.rb +0 -7
- data/lib/dry/validation/input_processor_compiler/json.rb +0 -45
- data/lib/dry/validation/input_processor_compiler/params.rb +0 -49
- data/lib/dry/validation/input_processor_compiler/sanitizer.rb +0 -47
- data/lib/dry/validation/input_processor_compiler.rb +0 -137
- data/lib/dry/validation/message_compiler/visitor_opts.rb +0 -37
- data/lib/dry/validation/message_compiler.rb +0 -188
- data/lib/dry/validation/messages/abstract.rb +0 -119
- data/lib/dry/validation/messages/i18n.rb +0 -47
- data/lib/dry/validation/messages/namespaced.rb +0 -39
- data/lib/dry/validation/messages/yaml.rb +0 -61
- data/lib/dry/validation/messages.rb +0 -14
- data/lib/dry/validation/predicate_registry.rb +0 -115
- data/lib/dry/validation/predicates.rb +0 -19
- data/lib/dry/validation/schema/check.rb +0 -37
- data/lib/dry/validation/schema/class_interface.rb +0 -190
- data/lib/dry/validation/schema/deprecated.rb +0 -30
- data/lib/dry/validation/schema/dsl.rb +0 -118
- data/lib/dry/validation/schema/form.rb +0 -9
- data/lib/dry/validation/schema/json.rb +0 -21
- data/lib/dry/validation/schema/key.rb +0 -71
- data/lib/dry/validation/schema/params.rb +0 -22
- data/lib/dry/validation/schema/rule.rb +0 -202
- data/lib/dry/validation/schema/value.rb +0 -211
- data/lib/dry/validation/schema.rb +0 -126
- data/lib/dry/validation/schema_compiler.rb +0 -81
- data/lib/dry/validation/template.rb +0 -66
- data/lib/dry/validation/type_specs.rb +0 -70
- data/log/.gitkeep +0 -0
- data/spec/extensions/monads/result_spec.rb +0 -40
- data/spec/extensions/struct/schema_spec.rb +0 -32
- data/spec/fixtures/locales/en.yml +0 -8
- data/spec/fixtures/locales/pl.yml +0 -22
- data/spec/integration/custom_error_messages_spec.rb +0 -54
- data/spec/integration/custom_predicates_spec.rb +0 -228
- data/spec/integration/hints_spec.rb +0 -170
- data/spec/integration/injecting_rules_spec.rb +0 -30
- data/spec/integration/json/defining_base_schema_spec.rb +0 -41
- data/spec/integration/localized_error_messages_spec.rb +0 -72
- data/spec/integration/message_compiler_spec.rb +0 -405
- data/spec/integration/messages/i18n_spec.rb +0 -104
- data/spec/integration/optional_keys_spec.rb +0 -28
- data/spec/integration/params/predicates/array_spec.rb +0 -287
- data/spec/integration/params/predicates/empty_spec.rb +0 -263
- data/spec/integration/params/predicates/eql_spec.rb +0 -327
- data/spec/integration/params/predicates/even_spec.rb +0 -455
- data/spec/integration/params/predicates/excluded_from_spec.rb +0 -455
- data/spec/integration/params/predicates/excludes_spec.rb +0 -391
- data/spec/integration/params/predicates/false_spec.rb +0 -455
- data/spec/integration/params/predicates/filled_spec.rb +0 -467
- data/spec/integration/params/predicates/format_spec.rb +0 -454
- data/spec/integration/params/predicates/gt_spec.rb +0 -519
- data/spec/integration/params/predicates/gteq_spec.rb +0 -519
- data/spec/integration/params/predicates/included_in_spec.rb +0 -455
- data/spec/integration/params/predicates/includes_spec.rb +0 -391
- data/spec/integration/params/predicates/key_spec.rb +0 -67
- data/spec/integration/params/predicates/lt_spec.rb +0 -519
- data/spec/integration/params/predicates/lteq_spec.rb +0 -519
- data/spec/integration/params/predicates/max_size_spec.rb +0 -391
- data/spec/integration/params/predicates/min_size_spec.rb +0 -391
- data/spec/integration/params/predicates/none_spec.rb +0 -265
- data/spec/integration/params/predicates/not_eql_spec.rb +0 -327
- data/spec/integration/params/predicates/odd_spec.rb +0 -455
- data/spec/integration/params/predicates/size/fixed_spec.rb +0 -393
- data/spec/integration/params/predicates/size/range_spec.rb +0 -396
- data/spec/integration/params/predicates/true_spec.rb +0 -455
- data/spec/integration/params/predicates/type_spec.rb +0 -391
- data/spec/integration/result_spec.rb +0 -81
- data/spec/integration/schema/array_schema_spec.rb +0 -59
- data/spec/integration/schema/check_rules_spec.rb +0 -119
- data/spec/integration/schema/check_with_nested_el_spec.rb +0 -37
- data/spec/integration/schema/check_with_nth_el_spec.rb +0 -25
- data/spec/integration/schema/default_settings_spec.rb +0 -11
- data/spec/integration/schema/defining_base_schema_spec.rb +0 -41
- data/spec/integration/schema/dynamic_predicate_args_spec.rb +0 -43
- data/spec/integration/schema/each_with_set_spec.rb +0 -70
- data/spec/integration/schema/extending_dsl_spec.rb +0 -27
- data/spec/integration/schema/form_spec.rb +0 -236
- data/spec/integration/schema/hash_schema_spec.rb +0 -47
- data/spec/integration/schema/inheriting_schema_spec.rb +0 -31
- data/spec/integration/schema/input_processor_spec.rb +0 -46
- data/spec/integration/schema/json/explicit_types_spec.rb +0 -157
- data/spec/integration/schema/json_spec.rb +0 -163
- data/spec/integration/schema/macros/confirmation_spec.rb +0 -35
- data/spec/integration/schema/macros/each_spec.rb +0 -268
- data/spec/integration/schema/macros/filled_spec.rb +0 -87
- data/spec/integration/schema/macros/input_spec.rb +0 -139
- data/spec/integration/schema/macros/maybe_spec.rb +0 -99
- data/spec/integration/schema/macros/rule_spec.rb +0 -75
- data/spec/integration/schema/macros/value_spec.rb +0 -119
- data/spec/integration/schema/macros/when_spec.rb +0 -62
- data/spec/integration/schema/nested_schemas_spec.rb +0 -236
- data/spec/integration/schema/nested_values_spec.rb +0 -46
- data/spec/integration/schema/not_spec.rb +0 -34
- data/spec/integration/schema/numbers_spec.rb +0 -19
- data/spec/integration/schema/option_with_default_spec.rb +0 -64
- data/spec/integration/schema/or_spec.rb +0 -87
- data/spec/integration/schema/params/defining_base_schema_spec.rb +0 -41
- data/spec/integration/schema/params/explicit_types_spec.rb +0 -195
- data/spec/integration/schema/params_spec.rb +0 -234
- data/spec/integration/schema/predicate_verification_spec.rb +0 -9
- data/spec/integration/schema/predicates/array_spec.rb +0 -295
- data/spec/integration/schema/predicates/custom_spec.rb +0 -103
- data/spec/integration/schema/predicates/empty_spec.rb +0 -263
- data/spec/integration/schema/predicates/eql_spec.rb +0 -327
- data/spec/integration/schema/predicates/even_spec.rb +0 -455
- data/spec/integration/schema/predicates/excluded_from/array_spec.rb +0 -459
- data/spec/integration/schema/predicates/excluded_from/range_spec.rb +0 -459
- data/spec/integration/schema/predicates/excludes_spec.rb +0 -391
- data/spec/integration/schema/predicates/filled_spec.rb +0 -467
- data/spec/integration/schema/predicates/format_spec.rb +0 -455
- data/spec/integration/schema/predicates/gt_spec.rb +0 -519
- data/spec/integration/schema/predicates/gteq_spec.rb +0 -519
- data/spec/integration/schema/predicates/hash_spec.rb +0 -69
- data/spec/integration/schema/predicates/included_in/array_spec.rb +0 -459
- data/spec/integration/schema/predicates/included_in/range_spec.rb +0 -459
- data/spec/integration/schema/predicates/includes_spec.rb +0 -391
- data/spec/integration/schema/predicates/key_spec.rb +0 -88
- data/spec/integration/schema/predicates/lt_spec.rb +0 -520
- data/spec/integration/schema/predicates/lteq_spec.rb +0 -519
- data/spec/integration/schema/predicates/max_size_spec.rb +0 -391
- data/spec/integration/schema/predicates/min_size_spec.rb +0 -391
- data/spec/integration/schema/predicates/none_spec.rb +0 -265
- data/spec/integration/schema/predicates/not_eql_spec.rb +0 -391
- data/spec/integration/schema/predicates/odd_spec.rb +0 -455
- data/spec/integration/schema/predicates/size/fixed_spec.rb +0 -398
- data/spec/integration/schema/predicates/size/range_spec.rb +0 -395
- data/spec/integration/schema/predicates/type_spec.rb +0 -413
- data/spec/integration/schema/reusing_schema_spec.rb +0 -33
- data/spec/integration/schema/using_types_spec.rb +0 -135
- data/spec/integration/schema/validate_spec.rb +0 -120
- data/spec/integration/schema/xor_spec.rb +0 -35
- data/spec/integration/schema_builders_spec.rb +0 -17
- data/spec/integration/schema_spec.rb +0 -173
- data/spec/shared/message_compiler.rb +0 -11
- data/spec/shared/predicate_helper.rb +0 -15
- data/spec/shared/rule_compiler.rb +0 -8
- data/spec/spec_helper.rb +0 -62
- data/spec/support/define_struct.rb +0 -25
- data/spec/support/matchers.rb +0 -38
- data/spec/support/mutant.rb +0 -9
- data/spec/support/predicates_integration.rb +0 -7
- data/spec/unit/input_processor_compiler/json_spec.rb +0 -283
- data/spec/unit/input_processor_compiler/params_spec.rb +0 -328
- data/spec/unit/message_compiler/visit_failure_spec.rb +0 -38
- data/spec/unit/message_compiler/visit_spec.rb +0 -16
- data/spec/unit/message_compiler_spec.rb +0 -7
- data/spec/unit/predicate_registry_spec.rb +0 -34
- data/spec/unit/schema/key_spec.rb +0 -38
- data/spec/unit/schema/rule_spec.rb +0 -42
- data/spec/unit/schema/value_spec.rb +0 -131
- data/spec/unit/schema_spec.rb +0 -35
@@ -0,0 +1,224 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/initializer"
|
4
|
+
require "dry/core/deprecations"
|
5
|
+
|
6
|
+
require "dry/validation/constants"
|
7
|
+
require "dry/validation/failures"
|
8
|
+
|
9
|
+
module Dry
|
10
|
+
module Validation
|
11
|
+
# Evaluator is the execution context for rules
|
12
|
+
#
|
13
|
+
# Evaluators expose an API for setting failure messages and forward
|
14
|
+
# method calls to the contracts, so that you can use your contract
|
15
|
+
# methods within rule blocks
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
class Evaluator
|
19
|
+
extend Dry::Initializer
|
20
|
+
extend Dry::Core::Deprecations[:'dry-validation']
|
21
|
+
|
22
|
+
deprecate :error?, :schema_error?
|
23
|
+
|
24
|
+
# @!attribute [r] _contract
|
25
|
+
# @return [Contract]
|
26
|
+
# @api private
|
27
|
+
param :_contract
|
28
|
+
|
29
|
+
# @!attribute [r] result
|
30
|
+
# @return [Result]
|
31
|
+
# @api private
|
32
|
+
option :result
|
33
|
+
|
34
|
+
# @!attribute [r] keys
|
35
|
+
# @return [Array<String, Symbol, Hash>]
|
36
|
+
# @api private
|
37
|
+
option :keys
|
38
|
+
|
39
|
+
# @!attribute [r] macros
|
40
|
+
# @return [Array<Symbol>]
|
41
|
+
# @api private
|
42
|
+
option :macros, optional: true, default: proc { EMPTY_ARRAY.dup }
|
43
|
+
|
44
|
+
# @!attribute [r] _context
|
45
|
+
# @return [Concurrent::Map]
|
46
|
+
# @api private
|
47
|
+
option :_context
|
48
|
+
|
49
|
+
# @!attribute [r] path
|
50
|
+
# @return [Dry::Schema::Path]
|
51
|
+
# @api private
|
52
|
+
option :path, default: proc { Dry::Schema::Path[(key = keys.first) ? key : ROOT_PATH] }
|
53
|
+
|
54
|
+
# @!attribute [r] values
|
55
|
+
# @return [Object]
|
56
|
+
# @api private
|
57
|
+
option :values
|
58
|
+
|
59
|
+
# @!attribute [r] block_options
|
60
|
+
# @return [Hash<Symbol=>Symbol>]
|
61
|
+
# @api private
|
62
|
+
option :block_options, default: proc { EMPTY_HASH }
|
63
|
+
|
64
|
+
# @return [Hash]
|
65
|
+
attr_reader :_options
|
66
|
+
|
67
|
+
# Initialize a new evaluator
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
def initialize(contract, **options, &block)
|
71
|
+
super(contract, **options)
|
72
|
+
|
73
|
+
@_options = options
|
74
|
+
|
75
|
+
if block
|
76
|
+
exec_opts = block_options.map { |key, value| [key, _options[value]] }.to_h
|
77
|
+
instance_exec(**exec_opts, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
macros.each do |args|
|
81
|
+
macro = macro(*args.flatten(1))
|
82
|
+
instance_exec(**macro.extract_block_options(_options.merge(macro: macro)), ¯o.block)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Get `Failures` object for the default or provided path
|
87
|
+
#
|
88
|
+
# @param [Symbol,String,Hash,Array<Symbol>] path
|
89
|
+
#
|
90
|
+
# @return [Failures]
|
91
|
+
#
|
92
|
+
# @see Failures#failure
|
93
|
+
#
|
94
|
+
# @api public
|
95
|
+
def key(path = self.path)
|
96
|
+
(@key ||= EMPTY_HASH.dup)[path] ||= Failures.new(path)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Get `Failures` object for base errors
|
100
|
+
#
|
101
|
+
# @return [Failures]
|
102
|
+
#
|
103
|
+
# @see Failures#failure
|
104
|
+
#
|
105
|
+
# @api public
|
106
|
+
def base
|
107
|
+
@base ||= Failures.new
|
108
|
+
end
|
109
|
+
|
110
|
+
# Return aggregated failures
|
111
|
+
#
|
112
|
+
# @return [Array<Hash>]
|
113
|
+
#
|
114
|
+
# @api private
|
115
|
+
def failures
|
116
|
+
@failures ||= []
|
117
|
+
@failures += @base.opts if defined?(@base)
|
118
|
+
@failures.concat(@key.values.flat_map(&:opts)) if defined?(@key)
|
119
|
+
@failures
|
120
|
+
end
|
121
|
+
|
122
|
+
# @api private
|
123
|
+
def with(new_opts, &block)
|
124
|
+
self.class.new(_contract, **_options, **new_opts, &block)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Return default (first) key name
|
128
|
+
#
|
129
|
+
# @return [Symbol]
|
130
|
+
#
|
131
|
+
# @api public
|
132
|
+
def key_name
|
133
|
+
@key_name ||= keys.first
|
134
|
+
end
|
135
|
+
|
136
|
+
# Return the value found under the first specified key
|
137
|
+
#
|
138
|
+
# This is a convenient method that can be used in all the common cases
|
139
|
+
# where a rule depends on just one key and you want a quick access to
|
140
|
+
# the value
|
141
|
+
#
|
142
|
+
# @example
|
143
|
+
# rule(:age) do
|
144
|
+
# key.failure(:invalid) if value < 18
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# @return [Object]
|
148
|
+
#
|
149
|
+
# @api public
|
150
|
+
def value
|
151
|
+
values[key_name]
|
152
|
+
end
|
153
|
+
|
154
|
+
# Return if the value under the default key is available
|
155
|
+
#
|
156
|
+
# This is useful when dealing with rules for optional keys
|
157
|
+
#
|
158
|
+
# @example use the default key name
|
159
|
+
# rule(:age) do
|
160
|
+
# key.failure(:invalid) if key? && value < 18
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
# @example specify the key name
|
164
|
+
# rule(:start_date, :end_date) do
|
165
|
+
# if key?(:start_date) && !key?(:end_date)
|
166
|
+
# key(:end_date).failure("must provide an end_date with start_date")
|
167
|
+
# end
|
168
|
+
# end
|
169
|
+
#
|
170
|
+
# @return [Boolean]
|
171
|
+
#
|
172
|
+
# @api public
|
173
|
+
def key?(name = key_name)
|
174
|
+
values.key?(name)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Check if there are any errors on the schema under the provided path
|
178
|
+
#
|
179
|
+
# @param path [Symbol, String, Array] A Path-compatible spec
|
180
|
+
#
|
181
|
+
# @return [Boolean]
|
182
|
+
#
|
183
|
+
# @api public
|
184
|
+
def schema_error?(path)
|
185
|
+
result.schema_error?(path)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Check if there are any errors on the current rule
|
189
|
+
#
|
190
|
+
# @param path [Symbol, String, Array] A Path-compatible spec
|
191
|
+
#
|
192
|
+
# @return [Boolean]
|
193
|
+
#
|
194
|
+
# @api public
|
195
|
+
def rule_error?(path = nil)
|
196
|
+
if path.nil?
|
197
|
+
!key(self.path).empty?
|
198
|
+
else
|
199
|
+
result.rule_error?(path)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# @api private
|
204
|
+
def respond_to_missing?(meth, include_private = false)
|
205
|
+
super || _contract.respond_to?(meth, true)
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
# Forward to the underlying contract
|
211
|
+
#
|
212
|
+
# @api private
|
213
|
+
def method_missing(meth, *args, &block)
|
214
|
+
# yes, we do want to delegate to private methods too
|
215
|
+
if _contract.respond_to?(meth, true)
|
216
|
+
_contract.__send__(meth, *args, &block)
|
217
|
+
else
|
218
|
+
super
|
219
|
+
end
|
220
|
+
end
|
221
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Validation
|
5
|
+
# Hints extension
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# Dry::Validation.load_extensions(:hints)
|
9
|
+
#
|
10
|
+
# contract = Dry::Validation::Contract.build do
|
11
|
+
# schema do
|
12
|
+
# required(:name).filled(:string, min_size?: 2..4)
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# contract.call(name: "fo").hints
|
17
|
+
# # {:name=>["size must be within 2 - 4"]}
|
18
|
+
#
|
19
|
+
# contract.call(name: "").messages
|
20
|
+
# # {:name=>["must be filled", "size must be within 2 - 4"]}
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
module Hints
|
24
|
+
# Hints extensions for Result
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
module ResultExtensions
|
28
|
+
# Return error messages excluding hints
|
29
|
+
#
|
30
|
+
# @macro errors-options
|
31
|
+
# @return [MessageSet]
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
def errors(new_options = EMPTY_HASH)
|
35
|
+
opts = new_options.merge(hints: false)
|
36
|
+
@errors.with(schema_errors(opts), opts)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return errors and hints
|
40
|
+
#
|
41
|
+
# @macro errors-options
|
42
|
+
#
|
43
|
+
# @return [MessageSet]
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def messages(new_options = EMPTY_HASH)
|
47
|
+
errors.with(hints(new_options).to_a, options.merge(**new_options))
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return hint messages
|
51
|
+
#
|
52
|
+
# @macro errors-options
|
53
|
+
#
|
54
|
+
# @return [MessageSet]
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
def hints(new_options = EMPTY_HASH)
|
58
|
+
schema_result.hints(new_options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
Dry::Schema.load_extensions(:hints)
|
63
|
+
|
64
|
+
Result.prepend(ResultExtensions)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -1,18 +1,34 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/monads/result"
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
module Validation
|
7
|
+
# Monad extension for contract results
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# Dry::Validation.load_extensions(:monads)
|
11
|
+
#
|
12
|
+
# contract = Dry::Validation::Contract.build do
|
13
|
+
# schema do
|
14
|
+
# required(:name).filled(:string)
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# contract.call(name: nil).to_monad
|
19
|
+
#
|
20
|
+
# @api public
|
5
21
|
class Result
|
6
22
|
include Dry::Monads::Result::Mixin
|
7
23
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
24
|
+
# Returns a result monad
|
25
|
+
#
|
26
|
+
# @return [Dry::Monads::Result]
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def to_monad
|
30
|
+
success? ? Success(self) : Failure(self)
|
14
31
|
end
|
15
|
-
alias_method :to_either, :to_monad
|
16
32
|
end
|
17
33
|
end
|
18
34
|
end
|
@@ -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? format? 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
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/schema/path"
|
4
|
+
require "dry/validation/constants"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Validation
|
8
|
+
# Failure accumulator object
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
class Failures
|
12
|
+
# The path for messages accumulated by failures object
|
13
|
+
#
|
14
|
+
# @return [Dry::Schema::Path]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
attr_reader :path
|
18
|
+
|
19
|
+
# Options for messages
|
20
|
+
#
|
21
|
+
# These options are used by MessageResolver
|
22
|
+
#
|
23
|
+
# @return [Hash]
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
attr_reader :opts
|
27
|
+
|
28
|
+
# @api private
|
29
|
+
def initialize(path = ROOT_PATH)
|
30
|
+
@path = Dry::Schema::Path[path]
|
31
|
+
@opts = EMPTY_ARRAY.dup
|
32
|
+
end
|
33
|
+
|
34
|
+
# Set failure
|
35
|
+
#
|
36
|
+
# @overload failure(message)
|
37
|
+
# Set message text explicitly
|
38
|
+
# @param message [String] The message text
|
39
|
+
# @example
|
40
|
+
# failure('this failed')
|
41
|
+
#
|
42
|
+
# @overload failure(id)
|
43
|
+
# Use message identifier (needs localized messages setup)
|
44
|
+
# @param id [Symbol] The message id
|
45
|
+
# @example
|
46
|
+
# failure(:taken)
|
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
|
+
#
|
55
|
+
# @see Evaluator#key
|
56
|
+
# @see Evaluator#base
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
def failure(message, tokens = EMPTY_HASH)
|
60
|
+
opts << {message: message, tokens: tokens, path: path}
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
# @api private
|
65
|
+
def empty?
|
66
|
+
opts.empty?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/initializer"
|
4
|
+
require "dry/validation/constants"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Validation
|
8
|
+
# Abstract class for handling rule blocks
|
9
|
+
#
|
10
|
+
# @see Rule
|
11
|
+
# @see Macro
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
class Function
|
15
|
+
extend Dry::Initializer
|
16
|
+
|
17
|
+
# @!attribute [r] block
|
18
|
+
# @return [Proc]
|
19
|
+
# @api private
|
20
|
+
option :block
|
21
|
+
|
22
|
+
# @!attribute [r] block_options
|
23
|
+
# @return [Hash]
|
24
|
+
# @api private
|
25
|
+
option :block_options, default: -> { block ? map_keywords(block) : EMPTY_HASH }
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Extract options for the block kwargs
|
30
|
+
#
|
31
|
+
# @param [Proc] block Callable
|
32
|
+
# @return Hash
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
def map_keywords(block)
|
36
|
+
block
|
37
|
+
.parameters
|
38
|
+
.select { |arg,| arg.equal?(:keyreq) }
|
39
|
+
.map { |_, name| [name, BLOCK_OPTIONS_MAPPINGS[name]] }
|
40
|
+
.to_h
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/validation/constants"
|
4
|
+
require "dry/validation/function"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Validation
|
8
|
+
# A wrapper for macro validation blocks
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
class Macro < Function
|
12
|
+
# @!attribute [r] name
|
13
|
+
# @return [Symbol]
|
14
|
+
# @api public
|
15
|
+
param :name
|
16
|
+
|
17
|
+
# @!attribute [r] args
|
18
|
+
# @return [Array]
|
19
|
+
# @api public
|
20
|
+
option :args
|
21
|
+
|
22
|
+
# @!attribute [r] block
|
23
|
+
# @return [Proc]
|
24
|
+
# @api private
|
25
|
+
option :block
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
def with(args)
|
29
|
+
self.class.new(name, args: args, block: block)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
def extract_block_options(options)
|
34
|
+
block_options.map { |key, value| [key, options[value]] }.to_h
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/container"
|
4
|
+
require "dry/validation/macro"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Validation
|
8
|
+
# API for registering and accessing Rule macros
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
module Macros
|
12
|
+
module Registrar
|
13
|
+
# Register a macro
|
14
|
+
#
|
15
|
+
# @example register a global macro
|
16
|
+
# Dry::Validation.register_macro(:even_numbers) do
|
17
|
+
# key.failure('all numbers must be even') unless values[key_name].all?(&:even?)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# @example register a contract macro
|
21
|
+
# class MyContract < Dry::Validation::Contract
|
22
|
+
# register_macro(:even_numbers) do
|
23
|
+
# key.failure('all numbers must be even') unless values[key_name].all?(&:even?)
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# @param [Symbol] name The name of the macro
|
28
|
+
# @param [Array] args Optional default positional arguments for the macro
|
29
|
+
#
|
30
|
+
# @return [self]
|
31
|
+
#
|
32
|
+
# @see Macro
|
33
|
+
#
|
34
|
+
# @api public
|
35
|
+
def register_macro(name, *args, &block)
|
36
|
+
macros.register(name, *args, &block)
|
37
|
+
self
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Registry for macros
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
class Container
|
45
|
+
include Dry::Container::Mixin
|
46
|
+
|
47
|
+
# Register a new macro
|
48
|
+
#
|
49
|
+
# @example in a contract class
|
50
|
+
# class MyContract < Dry::Validation::Contract
|
51
|
+
# register_macro(:even_numbers) do
|
52
|
+
# key.failure('all numbers must be even') unless values[key_name].all?(&:even?)
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# @param [Symbol] name The name of the macro
|
57
|
+
#
|
58
|
+
# @return [self]
|
59
|
+
#
|
60
|
+
# @api public
|
61
|
+
def register(name, *args, &block)
|
62
|
+
macro = Macro.new(name, args: args, block: block)
|
63
|
+
super(name, macro, call: false, &nil)
|
64
|
+
self
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return a registered macro
|
69
|
+
#
|
70
|
+
# @param [Symbol] name The name of the macro
|
71
|
+
#
|
72
|
+
# @return [Proc]
|
73
|
+
#
|
74
|
+
# @api public
|
75
|
+
def self.[](name)
|
76
|
+
container[name]
|
77
|
+
end
|
78
|
+
|
79
|
+
# Register a global macro
|
80
|
+
#
|
81
|
+
# @see Container#register
|
82
|
+
#
|
83
|
+
# @return [Macros]
|
84
|
+
#
|
85
|
+
# @api public
|
86
|
+
def self.register(name, *args, &block)
|
87
|
+
container.register(name, *args, &block)
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
# @api private
|
92
|
+
def self.container
|
93
|
+
@container ||= Container.new
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Acceptance macro
|
98
|
+
#
|
99
|
+
# @api public
|
100
|
+
Macros.register(:acceptance) do
|
101
|
+
key.failure(:acceptance, key: key_name) unless values[key_name].equal?(true)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|