cmdx 2.0.0 → 2.1.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 +62 -8
- data/lib/cmdx/callbacks.rb +31 -11
- data/lib/cmdx/chain.rb +29 -10
- data/lib/cmdx/coercions/big_decimal.rb +1 -1
- data/lib/cmdx/coercions/boolean.rb +3 -9
- data/lib/cmdx/coercions/coerce.rb +4 -1
- data/lib/cmdx/coercions/date_time.rb +1 -1
- data/lib/cmdx/coercions/integer.rb +11 -2
- data/lib/cmdx/coercions/symbol.rb +23 -4
- data/lib/cmdx/coercions.rb +25 -10
- data/lib/cmdx/configuration.rb +31 -16
- data/lib/cmdx/context.rb +40 -56
- data/lib/cmdx/deprecation.rb +4 -7
- data/lib/cmdx/deprecators/error.rb +4 -1
- data/lib/cmdx/deprecators.rb +17 -8
- data/lib/cmdx/errors.rb +15 -11
- data/lib/cmdx/executors/fiber.rb +16 -4
- data/lib/cmdx/executors/thread.rb +18 -4
- data/lib/cmdx/executors.rb +22 -7
- data/lib/cmdx/fault.rb +15 -3
- data/lib/cmdx/i18n_proxy.rb +10 -6
- data/lib/cmdx/input.rb +23 -21
- data/lib/cmdx/inputs.rb +14 -26
- data/lib/cmdx/log_formatters/json.rb +8 -1
- data/lib/cmdx/log_formatters/logstash.rb +7 -1
- data/lib/cmdx/mergers.rb +22 -7
- data/lib/cmdx/middlewares.rb +40 -24
- data/lib/cmdx/output.rb +5 -2
- data/lib/cmdx/pipeline.rb +28 -11
- data/lib/cmdx/railtie.rb +1 -0
- data/lib/cmdx/result.rb +22 -6
- data/lib/cmdx/retriers/decorrelated_jitter.rb +10 -5
- data/lib/cmdx/retriers/exponential.rb +10 -2
- data/lib/cmdx/retriers/fibonacci.rb +29 -12
- data/lib/cmdx/retriers.rb +17 -8
- data/lib/cmdx/retry.rb +20 -13
- data/lib/cmdx/runtime.rb +22 -40
- data/lib/cmdx/settings.rb +9 -9
- data/lib/cmdx/signal.rb +1 -1
- data/lib/cmdx/task.rb +90 -45
- data/lib/cmdx/telemetry.rb +52 -11
- data/lib/cmdx/util.rb +50 -4
- data/lib/cmdx/validators/absence.rb +1 -1
- data/lib/cmdx/validators/exclusion.rb +15 -15
- data/lib/cmdx/validators/format.rb +12 -4
- data/lib/cmdx/validators/inclusion.rb +15 -15
- data/lib/cmdx/validators/length.rb +5 -49
- data/lib/cmdx/validators/numeric.rb +5 -49
- data/lib/cmdx/validators/presence.rb +1 -1
- data/lib/cmdx/validators/validate.rb +7 -1
- data/lib/cmdx/validators.rb +21 -9
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/workflow.rb +28 -14
- data/lib/cmdx.rb +24 -0
- data/lib/generators/cmdx/templates/install.rb +96 -33
- data/mkdocs.yml +2 -0
- metadata +1 -1
data/lib/cmdx/telemetry.rb
CHANGED
|
@@ -7,7 +7,21 @@ module CMDx
|
|
|
7
7
|
class Telemetry
|
|
8
8
|
|
|
9
9
|
# Immutable event payload passed to subscribers.
|
|
10
|
-
Event = Data.define(:xid, :cid, :root, :type, :task, :tid, :name, :payload, :timestamp)
|
|
10
|
+
Event = Data.define(:xid, :cid, :root, :type, :task, :tid, :name, :payload, :timestamp) do
|
|
11
|
+
def self.build(task, name, root: false, payload: EMPTY_HASH)
|
|
12
|
+
new(
|
|
13
|
+
xid: Chain.current.xid,
|
|
14
|
+
cid: Chain.current.id,
|
|
15
|
+
root:,
|
|
16
|
+
type: task.class.type,
|
|
17
|
+
task: task.class,
|
|
18
|
+
tid: task.tid,
|
|
19
|
+
name:,
|
|
20
|
+
payload:,
|
|
21
|
+
timestamp: Time.now.utc
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
11
25
|
|
|
12
26
|
# Lifecycle event names Runtime emits.
|
|
13
27
|
EVENTS = %i[
|
|
@@ -43,11 +57,17 @@ module CMDx
|
|
|
43
57
|
subscriber = callable || block
|
|
44
58
|
|
|
45
59
|
if callable && block
|
|
46
|
-
raise ArgumentError, "provide either a callable or a block, not both"
|
|
60
|
+
raise ArgumentError, "subscriber: provide either a callable or a block, not both"
|
|
47
61
|
elsif !subscriber.respond_to?(:call)
|
|
48
|
-
raise ArgumentError,
|
|
62
|
+
raise ArgumentError, <<~MSG.chomp
|
|
63
|
+
subscriber must respond to #call (got #{subscriber.class}).
|
|
64
|
+
See https://drexed.github.io/cmdx/configuration/#telemetry
|
|
65
|
+
MSG
|
|
49
66
|
elsif !EVENTS.include?(event)
|
|
50
|
-
raise ArgumentError,
|
|
67
|
+
raise ArgumentError, <<~MSG.chomp
|
|
68
|
+
unknown telemetry event #{event.inspect}, must be one of #{EVENTS.inspect}.
|
|
69
|
+
See https://drexed.github.io/cmdx/configuration/#telemetry
|
|
70
|
+
MSG
|
|
51
71
|
end
|
|
52
72
|
|
|
53
73
|
(registry[event] ||= []) << subscriber
|
|
@@ -60,14 +80,20 @@ module CMDx
|
|
|
60
80
|
# @param event [Symbol] one of {EVENTS}
|
|
61
81
|
# @param callable [#call] the subscriber to remove
|
|
62
82
|
# @return [Telemetry] self for chaining
|
|
63
|
-
# @raise [
|
|
83
|
+
# @raise [UnknownEntryError] when `event` is unknown
|
|
64
84
|
def unsubscribe(event, callable)
|
|
65
|
-
|
|
85
|
+
unless EVENTS.include?(event)
|
|
86
|
+
raise UnknownEntryError, <<~MSG.chomp
|
|
87
|
+
unknown telemetry event #{event.inspect}, must be one of #{EVENTS.inspect}.
|
|
88
|
+
See https://drexed.github.io/cmdx/configuration/#telemetry
|
|
89
|
+
MSG
|
|
90
|
+
end
|
|
66
91
|
|
|
67
|
-
|
|
92
|
+
if (subscribers = registry[event])
|
|
93
|
+
subscribers.delete(callable)
|
|
94
|
+
registry.delete(event) if subscribers.empty?
|
|
95
|
+
end
|
|
68
96
|
|
|
69
|
-
registry[event].delete(callable)
|
|
70
|
-
registry.delete(event) if registry[event].empty?
|
|
71
97
|
self
|
|
72
98
|
end
|
|
73
99
|
|
|
@@ -77,6 +103,18 @@ module CMDx
|
|
|
77
103
|
registry.key?(event)
|
|
78
104
|
end
|
|
79
105
|
|
|
106
|
+
# @param event [Symbol]
|
|
107
|
+
# @return [#call]
|
|
108
|
+
# @raise [UnknownEntryError] when `event` isn't registered
|
|
109
|
+
def lookup(event)
|
|
110
|
+
registry[event] || begin
|
|
111
|
+
raise UnknownEntryError, <<~MSG.chomp
|
|
112
|
+
unknown telemetry event #{event.inspect}; registered: #{registry.keys.inspect}.
|
|
113
|
+
See https://drexed.github.io/cmdx/configuration/#telemetry
|
|
114
|
+
MSG
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
80
118
|
# @return [Boolean]
|
|
81
119
|
def empty?
|
|
82
120
|
registry.empty?
|
|
@@ -99,9 +137,12 @@ module CMDx
|
|
|
99
137
|
# @param payload [Event]
|
|
100
138
|
# @return [void]
|
|
101
139
|
def emit(event, payload)
|
|
102
|
-
return
|
|
140
|
+
return if empty?
|
|
141
|
+
|
|
142
|
+
subscribers = lookup(event)
|
|
143
|
+
return if subscribers.nil? || subscribers.empty?
|
|
103
144
|
|
|
104
|
-
subscribers.each { |
|
|
145
|
+
subscribers.each { |callable| callable.call(payload) }
|
|
105
146
|
end
|
|
106
147
|
|
|
107
148
|
end
|
data/lib/cmdx/util.rb
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module CMDx
|
|
4
|
-
# Shared helpers for
|
|
5
|
-
#
|
|
6
|
-
# booleans, symbols (method names), procs, and call-ables into a truth value.
|
|
4
|
+
# Shared helpers for `:if` / `:unless` gates, recursive hash merge/dup, and
|
|
5
|
+
# related tree utilities used across tasks, context, and i18n.
|
|
7
6
|
module Util
|
|
8
7
|
|
|
9
8
|
extend self
|
|
@@ -28,7 +27,8 @@ module CMDx
|
|
|
28
27
|
else
|
|
29
28
|
return condition.call(receiver, *args) if condition.respond_to?(:call)
|
|
30
29
|
|
|
31
|
-
raise ArgumentError,
|
|
30
|
+
raise ArgumentError,
|
|
31
|
+
"condition must be a Symbol, Proc, or respond to #call (got #{condition.class})"
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -69,5 +69,51 @@ module CMDx
|
|
|
69
69
|
unless?(condition_unless, receiver, *args)
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
# Recursively merges two Hash-like trees. When both values at a key are
|
|
73
|
+
# Hashes, they merge recursively; otherwise the right-hand value wins
|
|
74
|
+
# (last-write-wins). When either top-level operand is not a Hash, returns
|
|
75
|
+
# `rhs` unchanged — useful when folding unknown YAML roots.
|
|
76
|
+
#
|
|
77
|
+
# @param lhs [Object] left tree (typically a Hash)
|
|
78
|
+
# @param rhs [Object] right tree (typically a Hash)
|
|
79
|
+
# @return [Object] merged Hash or `rhs` when a Hash-only merge is impossible
|
|
80
|
+
def deep_merge(lhs, rhs)
|
|
81
|
+
return rhs unless lhs.is_a?(Hash) && rhs.is_a?(Hash)
|
|
82
|
+
|
|
83
|
+
lhs.merge(rhs) { |_key, l, r| deep_merge(l, r) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns a deep copy of `value`. Immutable scalars (`Numeric`, `Symbol`,
|
|
87
|
+
# booleans, `nil`) are returned as-is; `Hash` and `Array` are walked
|
|
88
|
+
# recursively; other objects use `#dup`, falling back to the original when
|
|
89
|
+
# `#dup` raises.
|
|
90
|
+
#
|
|
91
|
+
# @param value [Object]
|
|
92
|
+
# @return [Object]
|
|
93
|
+
def deep_dup(value)
|
|
94
|
+
case value
|
|
95
|
+
when Numeric, Symbol, TrueClass, FalseClass, NilClass
|
|
96
|
+
value
|
|
97
|
+
when Hash
|
|
98
|
+
value.each_with_object({}) { |(k, v), acc| acc[k] = deep_dup(v) }
|
|
99
|
+
when Array
|
|
100
|
+
value.map { |e| deep_dup(e) }
|
|
101
|
+
else
|
|
102
|
+
begin
|
|
103
|
+
value.dup
|
|
104
|
+
rescue StandardError
|
|
105
|
+
value
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Returns a string representation of `error` in the format `[Class] Message`.
|
|
111
|
+
#
|
|
112
|
+
# @param error [Exception]
|
|
113
|
+
# @return [String]
|
|
114
|
+
def to_error_s(error)
|
|
115
|
+
"[#{error.class}] #{error.message}"
|
|
116
|
+
end
|
|
117
|
+
|
|
72
118
|
end
|
|
73
119
|
end
|
|
@@ -19,22 +19,29 @@ module CMDx
|
|
|
19
19
|
# @raise [ArgumentError] when neither `:in` nor `:within` is given
|
|
20
20
|
def call(value, options = EMPTY_HASH)
|
|
21
21
|
values = options[:in] || options[:within]
|
|
22
|
-
|
|
22
|
+
if values.nil?
|
|
23
|
+
raise ArgumentError, <<~MSG.chomp
|
|
24
|
+
exclusion validator requires :in or :within (got #{options.keys.inspect}).
|
|
25
|
+
See https://drexed.github.io/cmdx/inputs/validations/#exclusion
|
|
26
|
+
MSG
|
|
27
|
+
elsif values.is_a?(Hash)
|
|
28
|
+
raise ArgumentError, <<~MSG.chomp
|
|
29
|
+
exclusion validator :in/:within does not accept a Hash; pass an Array,
|
|
30
|
+
Set, Range, or other Enumerable (e.g. `#{values.inspect}.keys`).
|
|
31
|
+
See https://drexed.github.io/cmdx/inputs/validations/#exclusion
|
|
32
|
+
MSG
|
|
33
|
+
end
|
|
23
34
|
|
|
24
35
|
if values.is_a?(Range)
|
|
25
36
|
within_failure(values.begin, values.end, options) if values.cover?(value)
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
else
|
|
38
|
+
enum = values.is_a?(Enumerable) ? values : [values]
|
|
39
|
+
of_failure(enum, options) if enum.any? { |v| v === value }
|
|
28
40
|
end
|
|
29
41
|
end
|
|
30
42
|
|
|
31
43
|
private
|
|
32
44
|
|
|
33
|
-
# @param values [Enumerable] collection rendered into the failure message
|
|
34
|
-
# @param options [Hash{Symbol => Object}]
|
|
35
|
-
# @option options [String] :of_message
|
|
36
|
-
# @option options [String] :message
|
|
37
|
-
# @return [Validators::Failure]
|
|
38
45
|
def of_failure(values, options)
|
|
39
46
|
values = values.map(&:inspect).join(", ")
|
|
40
47
|
message = options[:of_message] || options[:message]
|
|
@@ -43,13 +50,6 @@ module CMDx
|
|
|
43
50
|
Failure.new(message || I18nProxy.t("cmdx.validators.exclusion.of", values:))
|
|
44
51
|
end
|
|
45
52
|
|
|
46
|
-
# @param min [Object] range/exclusion lower bound
|
|
47
|
-
# @param max [Object] range/exclusion upper bound
|
|
48
|
-
# @param options [Hash{Symbol => Object}]
|
|
49
|
-
# @option options [String] :in_message
|
|
50
|
-
# @option options [String] :within_message
|
|
51
|
-
# @option options [String] :message
|
|
52
|
-
# @return [Validators::Failure]
|
|
53
53
|
def within_failure(min, max, options)
|
|
54
54
|
message = options[:in_message] || options[:within_message] || options[:message]
|
|
55
55
|
message %= { min:, max: } unless message.nil?
|
|
@@ -15,17 +15,25 @@ module CMDx
|
|
|
15
15
|
# @option options [String] :message override for the failure message
|
|
16
16
|
# @return [Validators::Failure, nil]
|
|
17
17
|
# @raise [ArgumentError] when neither `:with` nor `:without` is given
|
|
18
|
+
# @note Non-String values that do not respond to `#match?` fail with the
|
|
19
|
+
# regular format failure rather than raise `NoMethodError`. Coerce inputs
|
|
20
|
+
# to String beforehand when format checks are required.
|
|
18
21
|
def call(value, options = EMPTY_HASH)
|
|
22
|
+
str = value.nil? || value.respond_to?(:match?) ? value : value.to_s
|
|
23
|
+
|
|
19
24
|
match =
|
|
20
25
|
case options
|
|
21
26
|
in with:, without:
|
|
22
|
-
|
|
27
|
+
str&.match?(with) && !str&.match?(without)
|
|
23
28
|
in with:
|
|
24
|
-
|
|
29
|
+
str&.match?(with)
|
|
25
30
|
in without:
|
|
26
|
-
!
|
|
31
|
+
!str&.match?(without)
|
|
27
32
|
else
|
|
28
|
-
raise ArgumentError,
|
|
33
|
+
raise ArgumentError, <<~MSG.chomp
|
|
34
|
+
format validator requires :with and/or :without (got #{options.keys.inspect}).
|
|
35
|
+
See https://drexed.github.io/cmdx/inputs/validations/#format
|
|
36
|
+
MSG
|
|
29
37
|
end
|
|
30
38
|
|
|
31
39
|
return if match
|
|
@@ -19,22 +19,29 @@ module CMDx
|
|
|
19
19
|
# @raise [ArgumentError] when neither `:in` nor `:within` is given
|
|
20
20
|
def call(value, options = EMPTY_HASH)
|
|
21
21
|
values = options[:in] || options[:within]
|
|
22
|
-
|
|
22
|
+
if values.nil?
|
|
23
|
+
raise ArgumentError, <<~MSG.chomp
|
|
24
|
+
inclusion validator requires :in or :within (got #{options.keys.inspect}).
|
|
25
|
+
See https://drexed.github.io/cmdx/inputs/validations/#inclusion
|
|
26
|
+
MSG
|
|
27
|
+
elsif values.is_a?(Hash)
|
|
28
|
+
raise ArgumentError, <<~MSG.chomp
|
|
29
|
+
inclusion validator :in/:within does not accept a Hash; pass an Array,
|
|
30
|
+
Set, Range, or other Enumerable (e.g. `#{values.inspect}.keys`).
|
|
31
|
+
See https://drexed.github.io/cmdx/inputs/validations/#inclusion
|
|
32
|
+
MSG
|
|
33
|
+
end
|
|
23
34
|
|
|
24
35
|
if values.is_a?(Range)
|
|
25
36
|
within_failure(values.begin, values.end, options) unless values.cover?(value)
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
else
|
|
38
|
+
enum = values.is_a?(Enumerable) ? values : [values]
|
|
39
|
+
of_failure(enum, options) if enum.none? { |v| v === value }
|
|
28
40
|
end
|
|
29
41
|
end
|
|
30
42
|
|
|
31
43
|
private
|
|
32
44
|
|
|
33
|
-
# @param values [Enumerable] collection rendered into the failure message
|
|
34
|
-
# @param options [Hash{Symbol => Object}]
|
|
35
|
-
# @option options [String] :of_message
|
|
36
|
-
# @option options [String] :message
|
|
37
|
-
# @return [Validators::Failure]
|
|
38
45
|
def of_failure(values, options)
|
|
39
46
|
values = values.map(&:inspect).join(", ")
|
|
40
47
|
message = options[:of_message] || options[:message]
|
|
@@ -43,13 +50,6 @@ module CMDx
|
|
|
43
50
|
Failure.new(message || I18nProxy.t("cmdx.validators.inclusion.of", values:))
|
|
44
51
|
end
|
|
45
52
|
|
|
46
|
-
# @param min [Object] range/inclusion lower bound
|
|
47
|
-
# @param max [Object] range/inclusion upper bound
|
|
48
|
-
# @param options [Hash{Symbol => Object}]
|
|
49
|
-
# @option options [String] :in_message
|
|
50
|
-
# @option options [String] :within_message
|
|
51
|
-
# @option options [String] :message
|
|
52
|
-
# @return [Validators::Failure]
|
|
53
53
|
def within_failure(min, max, options)
|
|
54
54
|
message = options[:in_message] || options[:within_message] || options[:message]
|
|
55
55
|
message %= { min:, max: } unless message.nil?
|
|
@@ -63,28 +63,21 @@ module CMDx
|
|
|
63
63
|
in is_not:
|
|
64
64
|
is_not_failure(is_not, options) if length == is_not
|
|
65
65
|
else
|
|
66
|
-
raise ArgumentError,
|
|
66
|
+
raise ArgumentError, <<~MSG.chomp
|
|
67
|
+
unknown length validator options #{options.keys.inspect};
|
|
68
|
+
expected one of [:within, :not_within, :in, :not_in, :min, :max, :gt, :lt, :is, :is_not] (aliases: :gte, :lte, :eq, :not_eq).
|
|
69
|
+
See https://drexed.github.io/cmdx/inputs/validations/#length
|
|
70
|
+
MSG
|
|
67
71
|
end
|
|
68
72
|
end
|
|
69
73
|
|
|
70
74
|
private
|
|
71
75
|
|
|
72
|
-
# @param options [Hash{Symbol => Object}]
|
|
73
|
-
# @option options [String] :nil_message
|
|
74
|
-
# @option options [String] :message
|
|
75
|
-
# @return [Validators::Failure]
|
|
76
76
|
def nil_failure(options)
|
|
77
77
|
message = options[:nil_message] || options[:message]
|
|
78
78
|
Failure.new(message || I18nProxy.t("cmdx.validators.length.nil_value"))
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
# @param min [Object]
|
|
82
|
-
# @param max [Object]
|
|
83
|
-
# @param options [Hash{Symbol => Object}]
|
|
84
|
-
# @option options [String] :within_message
|
|
85
|
-
# @option options [String] :in_message
|
|
86
|
-
# @option options [String] :message
|
|
87
|
-
# @return [Validators::Failure]
|
|
88
81
|
def within_failure(min, max, options)
|
|
89
82
|
message = options[:within_message] || options[:in_message] || options[:message]
|
|
90
83
|
message %= { min:, max: } unless message.nil?
|
|
@@ -92,13 +85,6 @@ module CMDx
|
|
|
92
85
|
Failure.new(message || I18nProxy.t("cmdx.validators.length.within", min:, max:))
|
|
93
86
|
end
|
|
94
87
|
|
|
95
|
-
# @param min [Object]
|
|
96
|
-
# @param max [Object]
|
|
97
|
-
# @param options [Hash{Symbol => Object}]
|
|
98
|
-
# @option options [String] :not_within_message
|
|
99
|
-
# @option options [String] :not_in_message
|
|
100
|
-
# @option options [String] :message
|
|
101
|
-
# @return [Validators::Failure]
|
|
102
88
|
def not_within_failure(min, max, options)
|
|
103
89
|
message = options[:not_within_message] || options[:not_in_message] || options[:message]
|
|
104
90
|
message %= { min:, max: } unless message.nil?
|
|
@@ -106,11 +92,6 @@ module CMDx
|
|
|
106
92
|
Failure.new(message || I18nProxy.t("cmdx.validators.length.not_within", min:, max:))
|
|
107
93
|
end
|
|
108
94
|
|
|
109
|
-
# @param min [Object]
|
|
110
|
-
# @param options [Hash{Symbol => Object}]
|
|
111
|
-
# @option options [String] :min_message
|
|
112
|
-
# @option options [String] :message
|
|
113
|
-
# @return [Validators::Failure]
|
|
114
95
|
def min_failure(min, options)
|
|
115
96
|
message = options[:min_message] || options[:message]
|
|
116
97
|
message %= { min: } unless message.nil?
|
|
@@ -118,11 +99,6 @@ module CMDx
|
|
|
118
99
|
Failure.new(message || I18nProxy.t("cmdx.validators.length.min", min:))
|
|
119
100
|
end
|
|
120
101
|
|
|
121
|
-
# @param max [Object]
|
|
122
|
-
# @param options [Hash{Symbol => Object}]
|
|
123
|
-
# @option options [String] :max_message
|
|
124
|
-
# @option options [String] :message
|
|
125
|
-
# @return [Validators::Failure]
|
|
126
102
|
def max_failure(max, options)
|
|
127
103
|
message = options[:max_message] || options[:message]
|
|
128
104
|
message %= { max: } unless message.nil?
|
|
@@ -130,11 +106,6 @@ module CMDx
|
|
|
130
106
|
Failure.new(message || I18nProxy.t("cmdx.validators.length.max", max:))
|
|
131
107
|
end
|
|
132
108
|
|
|
133
|
-
# @param gt [Object]
|
|
134
|
-
# @param options [Hash{Symbol => Object}]
|
|
135
|
-
# @option options [String] :gt_message
|
|
136
|
-
# @option options [String] :message
|
|
137
|
-
# @return [Validators::Failure]
|
|
138
109
|
def gt_failure(gt, options)
|
|
139
110
|
message = options[:gt_message] || options[:message]
|
|
140
111
|
message %= { gt: } unless message.nil?
|
|
@@ -142,11 +113,6 @@ module CMDx
|
|
|
142
113
|
Failure.new(message || I18nProxy.t("cmdx.validators.length.gt", gt:))
|
|
143
114
|
end
|
|
144
115
|
|
|
145
|
-
# @param lt [Object]
|
|
146
|
-
# @param options [Hash{Symbol => Object}]
|
|
147
|
-
# @option options [String] :lt_message
|
|
148
|
-
# @option options [String] :message
|
|
149
|
-
# @return [Validators::Failure]
|
|
150
116
|
def lt_failure(lt, options)
|
|
151
117
|
message = options[:lt_message] || options[:message]
|
|
152
118
|
message %= { lt: } unless message.nil?
|
|
@@ -154,11 +120,6 @@ module CMDx
|
|
|
154
120
|
Failure.new(message || I18nProxy.t("cmdx.validators.length.lt", lt:))
|
|
155
121
|
end
|
|
156
122
|
|
|
157
|
-
# @param is [Object]
|
|
158
|
-
# @param options [Hash{Symbol => Object}]
|
|
159
|
-
# @option options [String] :is_message
|
|
160
|
-
# @option options [String] :message
|
|
161
|
-
# @return [Validators::Failure]
|
|
162
123
|
def is_failure(is, options) # rubocop:disable Naming/PredicatePrefix
|
|
163
124
|
message = options[:is_message] || options[:message]
|
|
164
125
|
message %= { is: } unless message.nil?
|
|
@@ -166,11 +127,6 @@ module CMDx
|
|
|
166
127
|
Failure.new(message || I18nProxy.t("cmdx.validators.length.is", is:))
|
|
167
128
|
end
|
|
168
129
|
|
|
169
|
-
# @param is_not [Object]
|
|
170
|
-
# @param options [Hash{Symbol => Object}]
|
|
171
|
-
# @option options [String] :is_not_message
|
|
172
|
-
# @option options [String] :message
|
|
173
|
-
# @return [Validators::Failure]
|
|
174
130
|
def is_not_failure(is_not, options) # rubocop:disable Naming/PredicatePrefix
|
|
175
131
|
message = options[:is_not_message] || options[:message]
|
|
176
132
|
message %= { is_not: } unless message.nil?
|
|
@@ -60,28 +60,21 @@ module CMDx
|
|
|
60
60
|
in is_not:
|
|
61
61
|
is_not_failure(is_not, options) if value == is_not
|
|
62
62
|
else
|
|
63
|
-
raise ArgumentError,
|
|
63
|
+
raise ArgumentError, <<~MSG.chomp
|
|
64
|
+
unknown numeric validator options #{options.keys.inspect};
|
|
65
|
+
expected one of [:within, :not_within, :in, :not_in, :min, :max, :gt, :lt, :is, :is_not] (aliases: :gte, :lte, :eq, :not_eq).
|
|
66
|
+
See https://drexed.github.io/cmdx/inputs/validations/#numeric
|
|
67
|
+
MSG
|
|
64
68
|
end
|
|
65
69
|
end
|
|
66
70
|
|
|
67
71
|
private
|
|
68
72
|
|
|
69
|
-
# @param options [Hash{Symbol => Object}]
|
|
70
|
-
# @option options [String] :nil_message
|
|
71
|
-
# @option options [String] :message
|
|
72
|
-
# @return [Validators::Failure]
|
|
73
73
|
def nil_failure(options)
|
|
74
74
|
message = options[:nil_message] || options[:message]
|
|
75
75
|
Failure.new(message || I18nProxy.t("cmdx.validators.numeric.nil_value"))
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
# @param min [Object]
|
|
79
|
-
# @param max [Object]
|
|
80
|
-
# @param options [Hash{Symbol => Object}]
|
|
81
|
-
# @option options [String] :within_message
|
|
82
|
-
# @option options [String] :in_message
|
|
83
|
-
# @option options [String] :message
|
|
84
|
-
# @return [Validators::Failure]
|
|
85
78
|
def within_failure(min, max, options)
|
|
86
79
|
message = options[:within_message] || options[:in_message] || options[:message]
|
|
87
80
|
message %= { min:, max: } unless message.nil?
|
|
@@ -89,13 +82,6 @@ module CMDx
|
|
|
89
82
|
Failure.new(message || I18nProxy.t("cmdx.validators.numeric.within", min:, max:))
|
|
90
83
|
end
|
|
91
84
|
|
|
92
|
-
# @param min [Object]
|
|
93
|
-
# @param max [Object]
|
|
94
|
-
# @param options [Hash{Symbol => Object}]
|
|
95
|
-
# @option options [String] :not_within_message
|
|
96
|
-
# @option options [String] :not_in_message
|
|
97
|
-
# @option options [String] :message
|
|
98
|
-
# @return [Validators::Failure]
|
|
99
85
|
def not_within_failure(min, max, options)
|
|
100
86
|
message = options[:not_within_message] || options[:not_in_message] || options[:message]
|
|
101
87
|
message %= { min:, max: } unless message.nil?
|
|
@@ -103,11 +89,6 @@ module CMDx
|
|
|
103
89
|
Failure.new(message || I18nProxy.t("cmdx.validators.numeric.not_within", min:, max:))
|
|
104
90
|
end
|
|
105
91
|
|
|
106
|
-
# @param min [Object]
|
|
107
|
-
# @param options [Hash{Symbol => Object}]
|
|
108
|
-
# @option options [String] :min_message
|
|
109
|
-
# @option options [String] :message
|
|
110
|
-
# @return [Validators::Failure]
|
|
111
92
|
def min_failure(min, options)
|
|
112
93
|
message = options[:min_message] || options[:message]
|
|
113
94
|
message %= { min: } unless message.nil?
|
|
@@ -115,11 +96,6 @@ module CMDx
|
|
|
115
96
|
Failure.new(message || I18nProxy.t("cmdx.validators.numeric.min", min:))
|
|
116
97
|
end
|
|
117
98
|
|
|
118
|
-
# @param max [Object]
|
|
119
|
-
# @param options [Hash{Symbol => Object}]
|
|
120
|
-
# @option options [String] :max_message
|
|
121
|
-
# @option options [String] :message
|
|
122
|
-
# @return [Validators::Failure]
|
|
123
99
|
def max_failure(max, options)
|
|
124
100
|
message = options[:max_message] || options[:message]
|
|
125
101
|
message %= { max: } unless message.nil?
|
|
@@ -127,11 +103,6 @@ module CMDx
|
|
|
127
103
|
Failure.new(message || I18nProxy.t("cmdx.validators.numeric.max", max:))
|
|
128
104
|
end
|
|
129
105
|
|
|
130
|
-
# @param gt [Object]
|
|
131
|
-
# @param options [Hash{Symbol => Object}]
|
|
132
|
-
# @option options [String] :gt_message
|
|
133
|
-
# @option options [String] :message
|
|
134
|
-
# @return [Validators::Failure]
|
|
135
106
|
def gt_failure(gt, options)
|
|
136
107
|
message = options[:gt_message] || options[:message]
|
|
137
108
|
message %= { gt: } unless message.nil?
|
|
@@ -139,11 +110,6 @@ module CMDx
|
|
|
139
110
|
Failure.new(message || I18nProxy.t("cmdx.validators.numeric.gt", gt:))
|
|
140
111
|
end
|
|
141
112
|
|
|
142
|
-
# @param lt [Object]
|
|
143
|
-
# @param options [Hash{Symbol => Object}]
|
|
144
|
-
# @option options [String] :lt_message
|
|
145
|
-
# @option options [String] :message
|
|
146
|
-
# @return [Validators::Failure]
|
|
147
113
|
def lt_failure(lt, options)
|
|
148
114
|
message = options[:lt_message] || options[:message]
|
|
149
115
|
message %= { lt: } unless message.nil?
|
|
@@ -151,11 +117,6 @@ module CMDx
|
|
|
151
117
|
Failure.new(message || I18nProxy.t("cmdx.validators.numeric.lt", lt:))
|
|
152
118
|
end
|
|
153
119
|
|
|
154
|
-
# @param is [Object]
|
|
155
|
-
# @param options [Hash{Symbol => Object}]
|
|
156
|
-
# @option options [String] :is_message
|
|
157
|
-
# @option options [String] :message
|
|
158
|
-
# @return [Validators::Failure]
|
|
159
120
|
def is_failure(is, options) # rubocop:disable Naming/PredicatePrefix
|
|
160
121
|
message = options[:is_message] || options[:message]
|
|
161
122
|
message %= { is: } unless message.nil?
|
|
@@ -163,11 +124,6 @@ module CMDx
|
|
|
163
124
|
Failure.new(message || I18nProxy.t("cmdx.validators.numeric.is", is:))
|
|
164
125
|
end
|
|
165
126
|
|
|
166
|
-
# @param is_not [Object]
|
|
167
|
-
# @param options [Hash{Symbol => Object}]
|
|
168
|
-
# @option options [String] :is_not_message
|
|
169
|
-
# @option options [String] :message
|
|
170
|
-
# @return [Validators::Failure]
|
|
171
127
|
def is_not_failure(is_not, options) # rubocop:disable Naming/PredicatePrefix
|
|
172
128
|
message = options[:is_not_message] || options[:message]
|
|
173
129
|
message %= { is_not: } unless message.nil?
|
|
@@ -13,6 +13,9 @@ module CMDx
|
|
|
13
13
|
# @param handler [Symbol, Proc, #call]
|
|
14
14
|
# @return [Validators::Failure, nil, Object] handler's return value
|
|
15
15
|
# @raise [ArgumentError] when `handler` isn't a supported type
|
|
16
|
+
# @note Symbol handlers are dispatched via `send` so private helpers on
|
|
17
|
+
# the task are reachable. Handlers are baked into class definitions;
|
|
18
|
+
# never derive them from untrusted input.
|
|
16
19
|
def call(task, value, handler)
|
|
17
20
|
case handler
|
|
18
21
|
when Symbol
|
|
@@ -22,7 +25,10 @@ module CMDx
|
|
|
22
25
|
else
|
|
23
26
|
return handler.call(value, task) if handler.respond_to?(:call)
|
|
24
27
|
|
|
25
|
-
raise ArgumentError,
|
|
28
|
+
raise ArgumentError, <<~MSG.chomp
|
|
29
|
+
validate handler must be a Symbol, Proc, or respond to #call (got #{handler.class}).
|
|
30
|
+
See https://drexed.github.io/cmdx/inputs/validations/#inline-validate-callable
|
|
31
|
+
MSG
|
|
26
32
|
end
|
|
27
33
|
end
|
|
28
34
|
|
data/lib/cmdx/validators.rb
CHANGED
|
@@ -45,9 +45,12 @@ module CMDx
|
|
|
45
45
|
validator = callable || block
|
|
46
46
|
|
|
47
47
|
if callable && block
|
|
48
|
-
raise ArgumentError, "provide either a callable or a block, not both"
|
|
48
|
+
raise ArgumentError, "validator: provide either a callable or a block, not both"
|
|
49
49
|
elsif !validator.respond_to?(:call)
|
|
50
|
-
raise ArgumentError,
|
|
50
|
+
raise ArgumentError, <<~MSG.chomp
|
|
51
|
+
validator must respond to #call (got #{validator.class}).
|
|
52
|
+
See https://drexed.github.io/cmdx/inputs/validations/#declarations
|
|
53
|
+
MSG
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
registry[name.to_sym] = validator
|
|
@@ -57,16 +60,25 @@ module CMDx
|
|
|
57
60
|
# @param name [Symbol]
|
|
58
61
|
# @return [Validators] self for chaining
|
|
59
62
|
def deregister(name)
|
|
60
|
-
registry.delete(name
|
|
63
|
+
registry.delete(name)
|
|
61
64
|
self
|
|
62
65
|
end
|
|
63
66
|
|
|
67
|
+
# @param name [Symbol]
|
|
68
|
+
# @return [Boolean] whether a validator is registered under `name`
|
|
69
|
+
def key?(name)
|
|
70
|
+
registry.key?(name)
|
|
71
|
+
end
|
|
72
|
+
|
|
64
73
|
# @param name [Symbol]
|
|
65
74
|
# @return [#call]
|
|
66
|
-
# @raise [
|
|
75
|
+
# @raise [UnknownEntryError] when `name` isn't registered
|
|
67
76
|
def lookup(name)
|
|
68
77
|
registry[name] || begin
|
|
69
|
-
raise
|
|
78
|
+
raise UnknownEntryError, <<~MSG.chomp
|
|
79
|
+
unknown validator #{name.inspect}; registered: #{registry.keys.inspect}.
|
|
80
|
+
See https://drexed.github.io/cmdx/inputs/validations/#built-in-validators
|
|
81
|
+
MSG
|
|
70
82
|
end
|
|
71
83
|
end
|
|
72
84
|
|
|
@@ -137,9 +149,6 @@ module CMDx
|
|
|
137
149
|
|
|
138
150
|
private
|
|
139
151
|
|
|
140
|
-
# @param raw_options [Object] truthy flag, Hash, Array, Regexp, etc. from a declaration
|
|
141
|
-
# @return [Hash{Symbol => Object}, nil] normalized rule options, or nil when disabled
|
|
142
|
-
# @raise [ArgumentError] when `raw_options` has an unsupported shape
|
|
143
152
|
def normalize_options(raw_options)
|
|
144
153
|
case raw_options
|
|
145
154
|
when FalseClass, NilClass
|
|
@@ -153,7 +162,10 @@ module CMDx
|
|
|
153
162
|
when Regexp
|
|
154
163
|
{ with: raw_options }
|
|
155
164
|
else
|
|
156
|
-
raise ArgumentError,
|
|
165
|
+
raise ArgumentError, <<~MSG.chomp
|
|
166
|
+
unsupported validator option format #{raw_options.inspect}; expected Boolean, Hash, Array, or Regexp.
|
|
167
|
+
See https://drexed.github.io/cmdx/inputs/validations/#common-options
|
|
168
|
+
MSG
|
|
157
169
|
end
|
|
158
170
|
end
|
|
159
171
|
|