dry-validation 0.13.3 → 1.4.2
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 +390 -95
- data/LICENSE +1 -1
- data/README.md +15 -12
- data/config/errors.yml +3 -88
- data/dry-validation.gemspec +30 -19
- 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 +222 -0
- data/lib/dry/validation/evaluator.rb +198 -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 +65 -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 +109 -88
- data/lib/dry/validation/messages/resolver.rb +106 -0
- data/lib/dry/validation/result.rb +168 -40
- data/lib/dry/validation/rule.rb +135 -0
- data/lib/dry/validation/schema_ext.rb +46 -0
- data/lib/dry/validation/values.rb +95 -0
- data/lib/dry/validation/version.rb +3 -1
- metadata +45 -335
- 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.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
|
@@ -1,122 +1,143 @@
|
|
|
1
|
-
|
|
2
|
-
module Validation
|
|
3
|
-
class MessageSet
|
|
4
|
-
include Enumerable
|
|
1
|
+
# frozen_string_literal: true
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
key? filled? none? bool?
|
|
8
|
-
str? int? float? decimal?
|
|
9
|
-
date? date_time? time? hash?
|
|
10
|
-
array? format?
|
|
11
|
-
).freeze
|
|
3
|
+
require 'dry/schema/message_set'
|
|
12
4
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def self.[](messages, options = EMPTY_HASH)
|
|
16
|
-
new(messages.flatten, options)
|
|
17
|
-
end
|
|
5
|
+
require 'dry/validation/constants'
|
|
6
|
+
require 'dry/validation/message'
|
|
18
7
|
|
|
8
|
+
module Dry
|
|
9
|
+
module Validation
|
|
10
|
+
# MessageSet is a specialized message set for handling validation messages
|
|
11
|
+
#
|
|
12
|
+
# @api public
|
|
13
|
+
class MessageSet < Schema::MessageSet
|
|
14
|
+
# Return the source set of messages used to produce final evaluated messages
|
|
15
|
+
#
|
|
16
|
+
# @return [Array<Message, Message::Localized, Schema::Message>]
|
|
17
|
+
#
|
|
18
|
+
# @api private
|
|
19
|
+
attr_reader :source_messages
|
|
20
|
+
|
|
21
|
+
# Configured locale
|
|
22
|
+
#
|
|
23
|
+
# @return [Symbol]
|
|
24
|
+
#
|
|
25
|
+
# @api public
|
|
26
|
+
attr_reader :locale
|
|
27
|
+
|
|
28
|
+
# @api private
|
|
19
29
|
def initialize(messages, options = EMPTY_HASH)
|
|
20
|
-
@
|
|
21
|
-
@
|
|
22
|
-
|
|
23
|
-
@paths = failures.map(&:path).uniq
|
|
24
|
-
@options = options
|
|
25
|
-
|
|
26
|
-
initialize_hints!
|
|
27
|
-
initialize_placeholders!
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def dump
|
|
31
|
-
root? ? to_a : to_h
|
|
30
|
+
@locale = options[:locale]
|
|
31
|
+
@source_messages = options.fetch(:source) { messages.dup }
|
|
32
|
+
super
|
|
32
33
|
end
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
# Return a new message set using updated options
|
|
36
|
+
#
|
|
37
|
+
# @return [MessageSet]
|
|
38
|
+
#
|
|
39
|
+
# @api private
|
|
40
|
+
def with(other, new_options = EMPTY_HASH)
|
|
41
|
+
return self if new_options.empty? && other.eql?(messages)
|
|
42
|
+
|
|
43
|
+
self.class.new(
|
|
44
|
+
other | select { |err| err.is_a?(Message) },
|
|
45
|
+
options.merge(source: source_messages, **new_options)
|
|
46
|
+
).freeze
|
|
36
47
|
end
|
|
37
48
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
# Add a new message
|
|
50
|
+
#
|
|
51
|
+
# This is used when result is being prepared
|
|
52
|
+
#
|
|
53
|
+
# @return [MessageSet]
|
|
54
|
+
#
|
|
55
|
+
# @api private
|
|
56
|
+
def add(message)
|
|
57
|
+
@empty = nil
|
|
58
|
+
source_messages << message
|
|
59
|
+
messages << message
|
|
60
|
+
initialize_placeholders!
|
|
61
|
+
self
|
|
44
62
|
end
|
|
45
63
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
# Filter message set using provided predicates
|
|
65
|
+
#
|
|
66
|
+
# This method is open to any predicate because messages can be anything that
|
|
67
|
+
# implements Message API, thus they can implement whatever predicates you
|
|
68
|
+
# may need.
|
|
69
|
+
#
|
|
70
|
+
# @example get a list of base messages
|
|
71
|
+
# message_set = contract.(input).errors
|
|
72
|
+
# message_set.filter(:base?)
|
|
73
|
+
#
|
|
74
|
+
# @param [Array<Symbol>] predicates
|
|
75
|
+
#
|
|
76
|
+
# @return [MessageSet]
|
|
77
|
+
#
|
|
78
|
+
# @api public
|
|
79
|
+
def filter(*predicates)
|
|
80
|
+
messages = select { |msg|
|
|
81
|
+
predicates.all? { |predicate| msg.respond_to?(predicate) && msg.public_send(predicate) }
|
|
82
|
+
}
|
|
83
|
+
self.class.new(messages)
|
|
49
84
|
end
|
|
50
85
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
86
|
+
# @api private
|
|
87
|
+
def freeze
|
|
88
|
+
source_messages.select { |err| err.respond_to?(:evaluate) }.each do |err|
|
|
89
|
+
idx = source_messages.index(err)
|
|
90
|
+
msg = err.evaluate(locale: locale, full: options[:full])
|
|
91
|
+
messages[idx] = msg
|
|
56
92
|
end
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def to_a
|
|
61
|
-
to_h.values.flatten
|
|
93
|
+
to_h
|
|
94
|
+
self
|
|
62
95
|
end
|
|
63
96
|
|
|
64
97
|
private
|
|
65
98
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
msgs.each do |msg|
|
|
71
|
-
node << msg
|
|
72
|
-
|
|
73
|
-
msg_hints = hint_groups[msg.path]
|
|
74
|
-
node.concat(msg_hints) if msg_hints
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
node.map!(&:to_s)
|
|
78
|
-
|
|
79
|
-
hash
|
|
80
|
-
end
|
|
99
|
+
# @api private
|
|
100
|
+
def unique_paths
|
|
101
|
+
source_messages.uniq(&:path).map(&:path)
|
|
81
102
|
end
|
|
82
103
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
node << msg
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
node.map!(&:to_s)
|
|
92
|
-
|
|
104
|
+
# @api private
|
|
105
|
+
def messages_map
|
|
106
|
+
@messages_map ||= reduce(placeholders) { |hash, msg|
|
|
107
|
+
node = msg.path.reduce(hash) { |a, e| a.is_a?(Hash) ? a[e] : a.last[e] }
|
|
108
|
+
(node[0].is_a?(::Array) ? node[0] : node) << msg.dump
|
|
93
109
|
hash
|
|
94
|
-
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def hint_groups
|
|
98
|
-
@hint_groups ||= hints.group_by(&:path)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def initialize_hints!
|
|
102
|
-
hints.reject! { |hint| HINT_EXCLUSION.include?(hint.predicate) }
|
|
110
|
+
}
|
|
103
111
|
end
|
|
104
112
|
|
|
113
|
+
# @api private
|
|
114
|
+
#
|
|
115
|
+
# rubocop:disable Metrics/AbcSize
|
|
116
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
|
105
117
|
def initialize_placeholders!
|
|
106
|
-
@placeholders =
|
|
118
|
+
@placeholders = unique_paths.sort_by(&:size).each_with_object(EMPTY_HASH.dup) { |path, hash|
|
|
107
119
|
curr_idx = 0
|
|
108
120
|
last_idx = path.size - 1
|
|
109
121
|
node = hash
|
|
110
122
|
|
|
111
|
-
while curr_idx <= last_idx
|
|
123
|
+
while curr_idx <= last_idx
|
|
112
124
|
key = path[curr_idx]
|
|
113
|
-
|
|
125
|
+
|
|
126
|
+
next_node =
|
|
127
|
+
if node.is_a?(Array) && key.is_a?(Symbol)
|
|
128
|
+
node_hash = (node << [] << {}).last
|
|
129
|
+
node_hash[key] || (node_hash[key] = curr_idx < last_idx ? {} : [])
|
|
130
|
+
else
|
|
131
|
+
node[key] || (node[key] = curr_idx < last_idx ? {} : [])
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
node = next_node
|
|
114
135
|
curr_idx += 1
|
|
115
136
|
end
|
|
116
|
-
|
|
117
|
-
hash
|
|
118
|
-
end
|
|
137
|
+
}
|
|
119
138
|
end
|
|
139
|
+
# rubocop:enable Metrics/AbcSize
|
|
140
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
|
120
141
|
end
|
|
121
142
|
end
|
|
122
143
|
end
|
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
# If message argument is a Hash, then it MUST have a :text key,
|
|
26
|
+
# which value will be used as the message value
|
|
27
|
+
#
|
|
28
|
+
# @return [Message, Message::Localized]
|
|
29
|
+
#
|
|
30
|
+
# @api public
|
|
31
|
+
def call(message:, tokens:, path:, meta: EMPTY_HASH)
|
|
32
|
+
case message
|
|
33
|
+
when Symbol
|
|
34
|
+
Message[->(**opts) { message(message, path: path, tokens: tokens, **opts) }, path, meta]
|
|
35
|
+
when String
|
|
36
|
+
Message[message, path, meta]
|
|
37
|
+
when Hash
|
|
38
|
+
meta = message.dup
|
|
39
|
+
text = meta.delete(:text) { |key|
|
|
40
|
+
raise ArgumentError, <<~STR
|
|
41
|
+
+message+ Hash must contain :#{key} key (#{message.inspect} given)
|
|
42
|
+
STR
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
call(message: text, tokens: tokens, path: path, meta: meta)
|
|
46
|
+
else
|
|
47
|
+
raise ArgumentError, <<~STR
|
|
48
|
+
+message+ must be either a Symbol, String or Hash (#{message.inspect} given)
|
|
49
|
+
STR
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
alias_method :[], :call
|
|
53
|
+
|
|
54
|
+
# Resolve a message
|
|
55
|
+
#
|
|
56
|
+
# @return [String]
|
|
57
|
+
#
|
|
58
|
+
# @api public
|
|
59
|
+
#
|
|
60
|
+
# rubocop:disable Metrics/AbcSize
|
|
61
|
+
def message(rule, tokens: EMPTY_HASH, locale: nil, full: false, path:)
|
|
62
|
+
keys = path.to_a.compact
|
|
63
|
+
msg_opts = tokens.merge(path: keys, locale: locale || messages.default_locale)
|
|
64
|
+
|
|
65
|
+
if keys.empty?
|
|
66
|
+
template, meta = messages["rules.#{rule}", msg_opts]
|
|
67
|
+
else
|
|
68
|
+
template, meta = messages[rule, msg_opts.merge(path: keys.join(DOT))]
|
|
69
|
+
template, meta = messages[rule, msg_opts.merge(path: keys.last)] unless template
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
unless template
|
|
73
|
+
raise MissingMessageError, <<~STR
|
|
74
|
+
Message template for #{rule.inspect} under #{keys.join(DOT).inspect} was not found
|
|
75
|
+
STR
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
parsed_tokens = parse_tokens(tokens)
|
|
79
|
+
text = template.(template.data(parsed_tokens))
|
|
80
|
+
|
|
81
|
+
[full ? "#{messages.rule(keys.last, msg_opts)} #{text}" : text, meta]
|
|
82
|
+
end
|
|
83
|
+
# rubocop:enable Metrics/AbcSize
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def parse_tokens(tokens)
|
|
88
|
+
Hash[
|
|
89
|
+
tokens.map do |key, token|
|
|
90
|
+
[key, parse_token(token)]
|
|
91
|
+
end
|
|
92
|
+
]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def parse_token(token)
|
|
96
|
+
case token
|
|
97
|
+
when Array
|
|
98
|
+
token.join(', ')
|
|
99
|
+
else
|
|
100
|
+
token
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -1,82 +1,210 @@
|
|
|
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)
|
|
107
|
+
end
|
|
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
|
+
|
|
125
|
+
# Add a new error for the provided key
|
|
126
|
+
#
|
|
127
|
+
# @api private
|
|
128
|
+
def add_error(error)
|
|
129
|
+
@errors.add(error)
|
|
130
|
+
self
|
|
42
131
|
end
|
|
43
132
|
|
|
44
|
-
|
|
45
|
-
|
|
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]
|
|
46
142
|
end
|
|
47
143
|
|
|
48
|
-
|
|
49
|
-
|
|
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)
|
|
50
153
|
end
|
|
51
154
|
|
|
52
|
-
|
|
53
|
-
|
|
155
|
+
# Coerce to a hash
|
|
156
|
+
#
|
|
157
|
+
# @api public
|
|
158
|
+
def to_h
|
|
159
|
+
values.to_h
|
|
54
160
|
end
|
|
55
161
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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}>"
|
|
59
168
|
else
|
|
60
|
-
|
|
169
|
+
"#<#{self.class}#{to_h} errors=#{errors.to_h} context=#{context.each.to_h}>"
|
|
61
170
|
end
|
|
62
171
|
end
|
|
63
172
|
|
|
64
|
-
|
|
65
|
-
|
|
173
|
+
# Freeze result and its error set
|
|
174
|
+
#
|
|
175
|
+
# @api private
|
|
176
|
+
def freeze
|
|
177
|
+
values.freeze
|
|
178
|
+
errors.freeze
|
|
179
|
+
super
|
|
66
180
|
end
|
|
67
181
|
|
|
68
|
-
|
|
69
|
-
|
|
182
|
+
if RUBY_VERSION >= '2.7'
|
|
183
|
+
# Pattern matching
|
|
184
|
+
#
|
|
185
|
+
# @api private
|
|
186
|
+
def deconstruct_keys(keys)
|
|
187
|
+
values.deconstruct_keys(keys)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Pattern matching
|
|
191
|
+
#
|
|
192
|
+
# @api private
|
|
193
|
+
def deconstruct
|
|
194
|
+
[values, context.each.to_h]
|
|
195
|
+
end
|
|
70
196
|
end
|
|
71
197
|
|
|
72
198
|
private
|
|
73
199
|
|
|
74
|
-
|
|
75
|
-
|
|
200
|
+
# @api private
|
|
201
|
+
def initialize_errors(options = self.options)
|
|
202
|
+
MessageSet.new(schema_errors(options), options)
|
|
76
203
|
end
|
|
77
204
|
|
|
78
|
-
|
|
79
|
-
|
|
205
|
+
# @api private
|
|
206
|
+
def schema_errors(options)
|
|
207
|
+
schema_result.message_set(options).to_a
|
|
80
208
|
end
|
|
81
209
|
end
|
|
82
210
|
end
|