dry-validation 0.13.3 → 1.3.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 +233 -12
- data/LICENSE +1 -1
- data/README.md +13 -9
- data/config/errors.yml +3 -88
- data/lib/dry-validation.rb +2 -0
- data/lib/dry/validation.rb +47 -28
- data/lib/dry/validation/config.rb +24 -0
- data/lib/dry/validation/constants.rb +43 -0
- data/lib/dry/validation/contract.rb +160 -0
- data/lib/dry/validation/contract/class_interface.rb +223 -0
- data/lib/dry/validation/evaluator.rb +197 -0
- data/lib/dry/validation/extensions/hints.rb +69 -0
- data/lib/dry/validation/extensions/monads.rb +23 -7
- data/lib/dry/validation/extensions/predicates_as_macros.rb +75 -0
- data/lib/dry/validation/failures.rb +58 -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 +79 -79
- data/lib/dry/validation/message_set.rb +108 -88
- data/lib/dry/validation/messages/resolver.rb +79 -0
- data/lib/dry/validation/result.rb +154 -42
- data/lib/dry/validation/rule.rb +129 -0
- data/lib/dry/validation/schema_ext.rb +46 -0
- data/lib/dry/validation/values.rb +94 -0
- data/lib/dry/validation/version.rb +3 -1
- metadata +41 -336
- 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/dry-validation.gemspec +0 -28
- 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.rb +0 -7
- data/lib/dry/validation/extensions/struct.rb +0 -32
- data/lib/dry/validation/input_processor_compiler.rb +0 -137
- 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/message_compiler.rb +0 -188
- data/lib/dry/validation/message_compiler/visitor_opts.rb +0 -37
- data/lib/dry/validation/messages.rb +0 -14
- 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/predicate_registry.rb +0 -115
- data/lib/dry/validation/predicates.rb +0 -19
- data/lib/dry/validation/schema.rb +0 -126
- 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_compiler.rb +0 -81
- data/lib/dry/validation/template.rb +0 -66
- data/lib/dry/validation/type_specs.rb +0 -70
- 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,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/validation/message'
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Validation
|
7
|
+
module Messages
|
8
|
+
# Resolve translated messages from failure arguments
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
class Resolver
|
12
|
+
# @!attribute [r] messages
|
13
|
+
# @return [Messages::I18n, Messages::YAML] messages backend
|
14
|
+
# @api private
|
15
|
+
attr_reader :messages
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
def initialize(messages)
|
19
|
+
@messages = messages
|
20
|
+
end
|
21
|
+
|
22
|
+
# Resolve Message object from provided args and path
|
23
|
+
#
|
24
|
+
# This is used internally by contracts when rules are applied
|
25
|
+
#
|
26
|
+
# @return [Message, Message::Localized]
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def call(message:, tokens:, path:, meta: EMPTY_HASH)
|
30
|
+
case message
|
31
|
+
when Symbol
|
32
|
+
Message[->(**opts) { message(message, path: path, tokens: tokens, **opts) }, path, meta]
|
33
|
+
when String
|
34
|
+
Message[message, path, meta]
|
35
|
+
when Hash
|
36
|
+
meta = message.dup
|
37
|
+
text = meta.delete(:text)
|
38
|
+
call(message: text, tokens: tokens, path: path, meta: meta)
|
39
|
+
else
|
40
|
+
raise ArgumentError, <<~STR
|
41
|
+
+message+ must be either a Symbol, String or Hash (#{message.inspect} given)
|
42
|
+
STR
|
43
|
+
end
|
44
|
+
end
|
45
|
+
alias_method :[], :call
|
46
|
+
|
47
|
+
# Resolve a message
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
#
|
51
|
+
# @api public
|
52
|
+
#
|
53
|
+
# rubocop:disable Metrics/AbcSize
|
54
|
+
def message(rule, tokens: EMPTY_HASH, locale: nil, full: false, path:)
|
55
|
+
keys = path.to_a.compact
|
56
|
+
msg_opts = tokens.merge(path: keys, locale: locale || messages.default_locale)
|
57
|
+
|
58
|
+
if keys.empty?
|
59
|
+
template, meta = messages["rules.#{rule}", msg_opts]
|
60
|
+
else
|
61
|
+
template, meta = messages[rule, msg_opts.merge(path: keys.join(DOT))]
|
62
|
+
template, meta = messages[rule, msg_opts.merge(path: keys.last)] unless template
|
63
|
+
end
|
64
|
+
|
65
|
+
unless template
|
66
|
+
raise MissingMessageError, <<~STR
|
67
|
+
Message template for #{rule.inspect} under #{keys.join(DOT).inspect} was not found
|
68
|
+
STR
|
69
|
+
end
|
70
|
+
|
71
|
+
text = template.(template.data(tokens))
|
72
|
+
|
73
|
+
[full ? "#{messages.rule(keys.last, msg_opts)} #{text}" : text, meta]
|
74
|
+
end
|
75
|
+
# rubocop:enable Metrics/AbcSize
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -1,82 +1,194 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent/map'
|
1
4
|
require 'dry/equalizer'
|
2
5
|
|
6
|
+
require 'dry/validation/constants'
|
7
|
+
require 'dry/validation/message_set'
|
8
|
+
require 'dry/validation/values'
|
9
|
+
|
3
10
|
module Dry
|
4
11
|
module Validation
|
12
|
+
# Result objects are returned by contracts
|
13
|
+
#
|
14
|
+
# @api public
|
5
15
|
class Result
|
6
|
-
include Dry::Equalizer(:
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
+
include Dry::Equalizer(:schema_result, :context, :errors, inspect: false)
|
17
|
+
|
18
|
+
# Build a new result
|
19
|
+
#
|
20
|
+
# @param [Dry::Schema::Result] schema_result
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
def self.new(schema_result, context = ::Concurrent::Map.new, options = EMPTY_HASH)
|
24
|
+
result = super
|
25
|
+
yield(result) if block_given?
|
26
|
+
result.freeze
|
27
|
+
end
|
16
28
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
29
|
+
# Context that's shared between rules
|
30
|
+
#
|
31
|
+
# @return [Concurrent::Map]
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
attr_reader :context
|
35
|
+
|
36
|
+
# Result from contract's schema
|
37
|
+
#
|
38
|
+
# @return [Dry::Schema::Result]
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
attr_reader :schema_result
|
42
|
+
|
43
|
+
# Result options
|
44
|
+
#
|
45
|
+
# @return [Hash]
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
attr_reader :options
|
49
|
+
|
50
|
+
# Initialize a new result
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
def initialize(schema_result, context, options)
|
54
|
+
@schema_result = schema_result
|
55
|
+
@context = context
|
56
|
+
@options = options
|
57
|
+
@errors = initialize_errors
|
22
58
|
end
|
23
59
|
|
24
|
-
|
25
|
-
|
60
|
+
# Return values wrapper with the input processed by schema
|
61
|
+
#
|
62
|
+
# @return [Values]
|
63
|
+
#
|
64
|
+
# @api public
|
65
|
+
def values
|
66
|
+
@values ||= Values.new(schema_result.to_h)
|
26
67
|
end
|
27
68
|
|
28
|
-
|
29
|
-
|
69
|
+
# Get error set
|
70
|
+
#
|
71
|
+
# @!macro errors-options
|
72
|
+
# @param [Hash] new_options
|
73
|
+
# @option new_options [Symbol] :locale Set locale for messages
|
74
|
+
# @option new_options [Boolean] :hints Enable/disable hints
|
75
|
+
# @option new_options [Boolean] :full Get messages that include key names
|
76
|
+
#
|
77
|
+
# @return [MessageSet]
|
78
|
+
#
|
79
|
+
# @api public
|
80
|
+
def errors(new_options = EMPTY_HASH)
|
81
|
+
new_options.empty? ? @errors : @errors.with(schema_errors(new_options), new_options)
|
30
82
|
end
|
31
83
|
|
84
|
+
# Check if result is successful
|
85
|
+
#
|
86
|
+
# @return [Bool]
|
87
|
+
#
|
88
|
+
# @api public
|
32
89
|
def success?
|
33
|
-
|
90
|
+
@errors.empty?
|
34
91
|
end
|
35
92
|
|
93
|
+
# Check if result is not successful
|
94
|
+
#
|
95
|
+
# @return [Bool]
|
96
|
+
#
|
97
|
+
# @api public
|
36
98
|
def failure?
|
37
99
|
!success?
|
38
100
|
end
|
39
101
|
|
40
|
-
|
41
|
-
|
102
|
+
# Check if values include an error for the provided key
|
103
|
+
#
|
104
|
+
# @api private
|
105
|
+
def error?(key)
|
106
|
+
schema_result.error?(key)
|
42
107
|
end
|
43
108
|
|
44
|
-
|
45
|
-
|
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
|
+
}
|
46
123
|
end
|
47
124
|
|
48
|
-
|
49
|
-
|
125
|
+
# Add a new error for the provided key
|
126
|
+
#
|
127
|
+
# @api private
|
128
|
+
def add_error(error)
|
129
|
+
@errors.add(error)
|
130
|
+
self
|
50
131
|
end
|
51
132
|
|
52
|
-
|
53
|
-
|
133
|
+
# Read a value under provided key
|
134
|
+
#
|
135
|
+
# @param [Symbol] key
|
136
|
+
#
|
137
|
+
# @return [Object]
|
138
|
+
#
|
139
|
+
# @api public
|
140
|
+
def [](key)
|
141
|
+
values[key]
|
54
142
|
end
|
55
143
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
144
|
+
# Check if a key was set
|
145
|
+
#
|
146
|
+
# @param [Symbol] key
|
147
|
+
#
|
148
|
+
# @return [Bool]
|
149
|
+
#
|
150
|
+
# @api public
|
151
|
+
def key?(key)
|
152
|
+
values.key?(key)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Coerce to a hash
|
156
|
+
#
|
157
|
+
# @api public
|
158
|
+
def to_h
|
159
|
+
values.to_h
|
62
160
|
end
|
63
161
|
|
64
|
-
|
65
|
-
|
162
|
+
# Return a string representation
|
163
|
+
#
|
164
|
+
# @api public
|
165
|
+
def inspect
|
166
|
+
if context.empty?
|
167
|
+
"#<#{self.class}#{to_h} errors=#{errors.to_h}>"
|
168
|
+
else
|
169
|
+
"#<#{self.class}#{to_h} errors=#{errors.to_h} context=#{context.each.to_h}>"
|
170
|
+
end
|
66
171
|
end
|
67
172
|
|
68
|
-
|
69
|
-
|
173
|
+
# Freeze result and its error set
|
174
|
+
#
|
175
|
+
# @api private
|
176
|
+
def freeze
|
177
|
+
values.freeze
|
178
|
+
errors.freeze
|
179
|
+
super
|
70
180
|
end
|
71
181
|
|
72
182
|
private
|
73
183
|
|
74
|
-
|
75
|
-
|
184
|
+
# @api private
|
185
|
+
def initialize_errors(options = self.options)
|
186
|
+
MessageSet.new(schema_errors(options), options)
|
76
187
|
end
|
77
188
|
|
78
|
-
|
79
|
-
|
189
|
+
# @api private
|
190
|
+
def schema_errors(options)
|
191
|
+
schema_result.message_set(options).to_a
|
80
192
|
end
|
81
193
|
end
|
82
194
|
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/equalizer'
|
4
|
+
|
5
|
+
require 'dry/validation/constants'
|
6
|
+
require 'dry/validation/function'
|
7
|
+
|
8
|
+
module Dry
|
9
|
+
module Validation
|
10
|
+
# Rules capture configuration and evaluator blocks
|
11
|
+
#
|
12
|
+
# When a rule is applied, it creates an `Evaluator` using schema result and its
|
13
|
+
# block will be evaluated in the context of the evaluator.
|
14
|
+
#
|
15
|
+
# @see Contract#rule
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
class Rule < Function
|
19
|
+
include Dry::Equalizer(:keys, :block, inspect: false)
|
20
|
+
|
21
|
+
# @!attribute [r] keys
|
22
|
+
# @return [Array<Symbol, String, Hash>]
|
23
|
+
# @api private
|
24
|
+
option :keys
|
25
|
+
|
26
|
+
# @!attribute [r] macros
|
27
|
+
# @return [Array<Symbol>]
|
28
|
+
# @api private
|
29
|
+
option :macros, default: proc { EMPTY_ARRAY.dup }
|
30
|
+
|
31
|
+
# Evaluate the rule within the provided context
|
32
|
+
#
|
33
|
+
# @param [Contract] contract
|
34
|
+
# @param [Result] result
|
35
|
+
#
|
36
|
+
# @api private
|
37
|
+
def call(contract, result)
|
38
|
+
Evaluator.new(
|
39
|
+
contract,
|
40
|
+
keys: keys,
|
41
|
+
macros: macros,
|
42
|
+
block_options: block_options,
|
43
|
+
result: result,
|
44
|
+
values: result.values,
|
45
|
+
_context: result.context,
|
46
|
+
&block
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Define which macros should be executed
|
51
|
+
#
|
52
|
+
# @see Contract#rule
|
53
|
+
# @return [Rule]
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def validate(*macros, &block)
|
57
|
+
@macros = parse_macros(*macros)
|
58
|
+
@block = block if block
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
# Define a validation function for each element of an array
|
63
|
+
#
|
64
|
+
# The function will be applied only if schema checks passed
|
65
|
+
# for a given array item.
|
66
|
+
#
|
67
|
+
# @example
|
68
|
+
# rule(:nums).each do
|
69
|
+
# key.failure("must be greater than 0") if value < 0
|
70
|
+
# end
|
71
|
+
# rule(:nums).each(min: 3)
|
72
|
+
# rule(address: :city) do
|
73
|
+
# key.failure("oops") if value != 'Munich'
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# @return [Rule]
|
77
|
+
#
|
78
|
+
# @api public
|
79
|
+
def each(*macros, &block)
|
80
|
+
root = keys[0]
|
81
|
+
macros = parse_macros(*macros)
|
82
|
+
@keys = []
|
83
|
+
|
84
|
+
@block = proc do
|
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]
|
88
|
+
|
89
|
+
next if result.error?(path)
|
90
|
+
|
91
|
+
evaluator = with(macros: macros, keys: [path], &block)
|
92
|
+
|
93
|
+
failures.concat(evaluator.failures)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
@block_options = map_keywords(block) if block
|
99
|
+
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
# Return a nice string representation
|
104
|
+
#
|
105
|
+
# @return [String]
|
106
|
+
#
|
107
|
+
# @api public
|
108
|
+
def inspect
|
109
|
+
%(#<#{self.class} keys=#{keys.inspect}>)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Parse function arguments into macros structure
|
113
|
+
#
|
114
|
+
# @return [Array]
|
115
|
+
#
|
116
|
+
# @api private
|
117
|
+
def parse_macros(*args)
|
118
|
+
args.each_with_object([]) do |spec, macros|
|
119
|
+
case spec
|
120
|
+
when Hash
|
121
|
+
spec.each { |k, v| macros << [k, Array(v)] }
|
122
|
+
else
|
123
|
+
macros << Array(spec)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -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.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
|
+
end
|
46
|
+
end
|