cmdx 1.21.0 → 2.0.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 +118 -1
- data/README.md +37 -24
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callbacks.rb +179 -0
- data/lib/cmdx/chain.rb +78 -175
- data/lib/cmdx/coercions/array.rb +19 -33
- data/lib/cmdx/coercions/big_decimal.rb +12 -29
- data/lib/cmdx/coercions/boolean.rb +25 -45
- data/lib/cmdx/coercions/coerce.rb +32 -0
- data/lib/cmdx/coercions/complex.rb +12 -27
- data/lib/cmdx/coercions/date.rb +29 -33
- data/lib/cmdx/coercions/date_time.rb +29 -33
- data/lib/cmdx/coercions/float.rb +8 -29
- data/lib/cmdx/coercions/hash.rb +17 -43
- data/lib/cmdx/coercions/integer.rb +8 -32
- data/lib/cmdx/coercions/rational.rb +12 -33
- data/lib/cmdx/coercions/string.rb +6 -24
- data/lib/cmdx/coercions/symbol.rb +12 -26
- data/lib/cmdx/coercions/time.rb +31 -35
- data/lib/cmdx/coercions.rb +174 -0
- data/lib/cmdx/configuration.rb +45 -237
- data/lib/cmdx/context.rb +264 -243
- data/lib/cmdx/deprecation.rb +67 -0
- data/lib/cmdx/deprecators/error.rb +22 -0
- data/lib/cmdx/deprecators/log.rb +22 -0
- data/lib/cmdx/deprecators/warn.rb +21 -0
- data/lib/cmdx/deprecators.rb +101 -0
- data/lib/cmdx/errors.rb +145 -79
- data/lib/cmdx/executors/fiber.rb +42 -0
- data/lib/cmdx/executors/thread.rb +36 -0
- data/lib/cmdx/executors.rb +95 -0
- data/lib/cmdx/fault.rb +85 -78
- data/lib/cmdx/i18n_proxy.rb +104 -0
- data/lib/cmdx/input.rb +294 -0
- data/lib/cmdx/inputs.rb +218 -0
- data/lib/cmdx/log_formatters/json.rb +9 -20
- data/lib/cmdx/log_formatters/key_value.rb +10 -21
- data/lib/cmdx/log_formatters/line.rb +7 -19
- data/lib/cmdx/log_formatters/logstash.rb +8 -21
- data/lib/cmdx/log_formatters/raw.rb +8 -20
- data/lib/cmdx/logger_proxy.rb +30 -0
- data/lib/cmdx/mergers/deep_merge.rb +23 -0
- data/lib/cmdx/mergers/last_write_wins.rb +23 -0
- data/lib/cmdx/mergers/no_merge.rb +20 -0
- data/lib/cmdx/mergers.rb +95 -0
- data/lib/cmdx/middlewares.rb +128 -0
- data/lib/cmdx/output.rb +115 -0
- data/lib/cmdx/outputs.rb +66 -0
- data/lib/cmdx/pipeline.rb +144 -131
- data/lib/cmdx/railtie.rb +10 -36
- data/lib/cmdx/result.rb +247 -524
- data/lib/cmdx/retriers/bounded_random.rb +24 -0
- data/lib/cmdx/retriers/decorrelated_jitter.rb +28 -0
- data/lib/cmdx/retriers/exponential.rb +23 -0
- data/lib/cmdx/retriers/fibonacci.rb +39 -0
- data/lib/cmdx/retriers/full_random.rb +23 -0
- data/lib/cmdx/retriers/half_random.rb +24 -0
- data/lib/cmdx/retriers/linear.rb +23 -0
- data/lib/cmdx/retriers.rb +106 -0
- data/lib/cmdx/retry.rb +117 -138
- data/lib/cmdx/runtime.rb +251 -0
- data/lib/cmdx/settings.rb +68 -200
- data/lib/cmdx/signal.rb +165 -0
- data/lib/cmdx/task.rb +443 -343
- data/lib/cmdx/telemetry.rb +108 -0
- data/lib/cmdx/util.rb +73 -0
- data/lib/cmdx/validators/absence.rb +10 -39
- data/lib/cmdx/validators/exclusion.rb +33 -52
- data/lib/cmdx/validators/format.rb +19 -49
- data/lib/cmdx/validators/inclusion.rb +33 -54
- data/lib/cmdx/validators/length.rb +125 -127
- data/lib/cmdx/validators/numeric.rb +123 -123
- data/lib/cmdx/validators/presence.rb +10 -39
- data/lib/cmdx/validators/validate.rb +31 -0
- data/lib/cmdx/validators.rb +161 -0
- data/lib/cmdx/version.rb +2 -4
- data/lib/cmdx/workflow.rb +71 -96
- data/lib/cmdx.rb +111 -42
- data/lib/generators/cmdx/install_generator.rb +7 -17
- data/lib/generators/cmdx/task_generator.rb +12 -29
- data/lib/generators/cmdx/templates/install.rb +120 -48
- data/lib/generators/cmdx/templates/task.rb.tt +1 -1
- data/lib/generators/cmdx/templates/workflow.rb.tt +1 -2
- data/lib/generators/cmdx/workflow_generator.rb +12 -29
- data/lib/locales/en.yml +8 -7
- data/mkdocs.yml +25 -23
- metadata +39 -138
- data/lib/cmdx/attribute.rb +0 -440
- data/lib/cmdx/attribute_registry.rb +0 -185
- data/lib/cmdx/attribute_value.rb +0 -252
- data/lib/cmdx/callback_registry.rb +0 -169
- data/lib/cmdx/coercion_registry.rb +0 -138
- data/lib/cmdx/deprecator.rb +0 -77
- data/lib/cmdx/exception.rb +0 -46
- data/lib/cmdx/executor.rb +0 -378
- data/lib/cmdx/identifier.rb +0 -30
- data/lib/cmdx/locale.rb +0 -78
- data/lib/cmdx/middleware_registry.rb +0 -148
- data/lib/cmdx/middlewares/correlate.rb +0 -140
- data/lib/cmdx/middlewares/runtime.rb +0 -77
- data/lib/cmdx/middlewares/timeout.rb +0 -78
- data/lib/cmdx/parallelizer.rb +0 -100
- data/lib/cmdx/utils/call.rb +0 -53
- data/lib/cmdx/utils/condition.rb +0 -71
- data/lib/cmdx/utils/format.rb +0 -82
- data/lib/cmdx/utils/normalize.rb +0 -52
- data/lib/cmdx/utils/wrap.rb +0 -38
- data/lib/cmdx/validator_registry.rb +0 -143
- data/lib/generators/cmdx/locale_generator.rb +0 -39
- data/lib/locales/af.yml +0 -55
- data/lib/locales/ar.yml +0 -55
- data/lib/locales/az.yml +0 -55
- data/lib/locales/be.yml +0 -55
- data/lib/locales/bg.yml +0 -55
- data/lib/locales/bn.yml +0 -55
- data/lib/locales/bs.yml +0 -55
- data/lib/locales/ca.yml +0 -55
- data/lib/locales/cnr.yml +0 -55
- data/lib/locales/cs.yml +0 -55
- data/lib/locales/cy.yml +0 -55
- data/lib/locales/da.yml +0 -55
- data/lib/locales/de.yml +0 -55
- data/lib/locales/dz.yml +0 -55
- data/lib/locales/el.yml +0 -55
- data/lib/locales/eo.yml +0 -55
- data/lib/locales/es.yml +0 -55
- data/lib/locales/et.yml +0 -55
- data/lib/locales/eu.yml +0 -55
- data/lib/locales/fa.yml +0 -55
- data/lib/locales/fi.yml +0 -55
- data/lib/locales/fr.yml +0 -55
- data/lib/locales/fy.yml +0 -55
- data/lib/locales/gd.yml +0 -55
- data/lib/locales/gl.yml +0 -55
- data/lib/locales/he.yml +0 -55
- data/lib/locales/hi.yml +0 -55
- data/lib/locales/hr.yml +0 -55
- data/lib/locales/hu.yml +0 -55
- data/lib/locales/hy.yml +0 -55
- data/lib/locales/id.yml +0 -55
- data/lib/locales/is.yml +0 -55
- data/lib/locales/it.yml +0 -55
- data/lib/locales/ja.yml +0 -55
- data/lib/locales/ka.yml +0 -55
- data/lib/locales/kk.yml +0 -55
- data/lib/locales/km.yml +0 -55
- data/lib/locales/kn.yml +0 -55
- data/lib/locales/ko.yml +0 -55
- data/lib/locales/lb.yml +0 -55
- data/lib/locales/lo.yml +0 -55
- data/lib/locales/lt.yml +0 -55
- data/lib/locales/lv.yml +0 -55
- data/lib/locales/mg.yml +0 -55
- data/lib/locales/mk.yml +0 -55
- data/lib/locales/ml.yml +0 -55
- data/lib/locales/mn.yml +0 -55
- data/lib/locales/mr-IN.yml +0 -55
- data/lib/locales/ms.yml +0 -55
- data/lib/locales/nb.yml +0 -55
- data/lib/locales/ne.yml +0 -55
- data/lib/locales/nl.yml +0 -55
- data/lib/locales/nn.yml +0 -55
- data/lib/locales/oc.yml +0 -55
- data/lib/locales/or.yml +0 -55
- data/lib/locales/pa.yml +0 -55
- data/lib/locales/pl.yml +0 -55
- data/lib/locales/pt.yml +0 -55
- data/lib/locales/rm.yml +0 -55
- data/lib/locales/ro.yml +0 -55
- data/lib/locales/ru.yml +0 -55
- data/lib/locales/sc.yml +0 -55
- data/lib/locales/sk.yml +0 -55
- data/lib/locales/sl.yml +0 -55
- data/lib/locales/sq.yml +0 -55
- data/lib/locales/sr.yml +0 -55
- data/lib/locales/st.yml +0 -55
- data/lib/locales/sv.yml +0 -55
- data/lib/locales/sw.yml +0 -55
- data/lib/locales/ta.yml +0 -55
- data/lib/locales/te.yml +0 -55
- data/lib/locales/th.yml +0 -55
- data/lib/locales/tl.yml +0 -55
- data/lib/locales/tr.yml +0 -55
- data/lib/locales/tt.yml +0 -55
- data/lib/locales/ug.yml +0 -55
- data/lib/locales/uk.yml +0 -55
- data/lib/locales/ur.yml +0 -55
- data/lib/locales/uz.yml +0 -55
- data/lib/locales/vi.yml +0 -55
- data/lib/locales/wo.yml +0 -55
- data/lib/locales/zh-CN.yml +0 -55
- data/lib/locales/zh-HK.yml +0 -55
- data/lib/locales/zh-TW.yml +0 -55
- data/lib/locales/zh-YUE.yml +0 -55
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module CMDx
|
|
4
|
-
# Manages a collection of attributes for task definition and verification.
|
|
5
|
-
# The registry provides methods to register, deregister, and process attributes
|
|
6
|
-
# in a hierarchical structure, supporting nested attribute definitions.
|
|
7
|
-
class AttributeRegistry
|
|
8
|
-
|
|
9
|
-
# Returns the collection of registered attributes.
|
|
10
|
-
#
|
|
11
|
-
# @return [Array<Attribute>] Array of registered attributes
|
|
12
|
-
#
|
|
13
|
-
# @example
|
|
14
|
-
# registry.registry # => [#<Attribute @name=:name>, #<Attribute @name=:email>]
|
|
15
|
-
#
|
|
16
|
-
# @rbs @registry: Array[Attribute]
|
|
17
|
-
attr_reader :registry
|
|
18
|
-
alias to_a registry
|
|
19
|
-
|
|
20
|
-
# Creates a new attribute registry with an optional initial collection.
|
|
21
|
-
#
|
|
22
|
-
# @param registry [Array<Attribute>] Initial attributes to register
|
|
23
|
-
#
|
|
24
|
-
# @return [AttributeRegistry] A new registry instance
|
|
25
|
-
#
|
|
26
|
-
# @example
|
|
27
|
-
# registry = AttributeRegistry.new
|
|
28
|
-
# registry = AttributeRegistry.new([attr1, attr2])
|
|
29
|
-
#
|
|
30
|
-
# @rbs (?Array[Attribute] registry) -> void
|
|
31
|
-
def initialize(registry = [])
|
|
32
|
-
@registry = registry
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Creates a duplicate of this registry with copied attributes.
|
|
36
|
-
#
|
|
37
|
-
# @return [AttributeRegistry] A new registry with duplicated attributes
|
|
38
|
-
#
|
|
39
|
-
# @example
|
|
40
|
-
# new_registry = registry.dup
|
|
41
|
-
#
|
|
42
|
-
# @rbs () -> AttributeRegistry
|
|
43
|
-
def dup
|
|
44
|
-
self.class.new(registry.dup)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Registers one or more attributes to the registry.
|
|
48
|
-
#
|
|
49
|
-
# @param attributes [Attribute, Array<Attribute>] Attribute(s) to register
|
|
50
|
-
#
|
|
51
|
-
# @return [AttributeRegistry] Self for method chaining
|
|
52
|
-
#
|
|
53
|
-
# @example
|
|
54
|
-
# registry.register(attribute)
|
|
55
|
-
# registry.register([attr1, attr2])
|
|
56
|
-
#
|
|
57
|
-
# @rbs (Attribute | Array[Attribute] attributes) -> self
|
|
58
|
-
def register(attributes)
|
|
59
|
-
@registry.concat(Utils::Wrap.array(attributes))
|
|
60
|
-
self
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Removes attributes from the registry by name.
|
|
64
|
-
# Supports hierarchical attribute removal by matching the entire attribute tree.
|
|
65
|
-
#
|
|
66
|
-
# @param names [Symbol, String, Array<Symbol, String>] Name(s) of attributes to remove
|
|
67
|
-
#
|
|
68
|
-
# @return [AttributeRegistry] Self for method chaining
|
|
69
|
-
#
|
|
70
|
-
# @example
|
|
71
|
-
# registry.deregister(:name)
|
|
72
|
-
# registry.deregister(['name1', 'name2'])
|
|
73
|
-
#
|
|
74
|
-
# @rbs ((Symbol | String | Array[Symbol | String]) names) -> self
|
|
75
|
-
def deregister(names)
|
|
76
|
-
Utils::Wrap.array(names).each do |name|
|
|
77
|
-
@registry.reject! { |attribute| matches_attribute_tree?(attribute, name.to_sym) }
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
self
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Eagerly defines attribute reader methods on the task class for all
|
|
84
|
-
# attributes whose method names can be statically resolved. Called at
|
|
85
|
-
# class definition time so readers are defined once, not per-execution.
|
|
86
|
-
#
|
|
87
|
-
# @param task_class [Class] The task class to define readers on
|
|
88
|
-
# @param attrs [Array<Attribute>] Attributes to process (defaults to all)
|
|
89
|
-
#
|
|
90
|
-
# @rbs (Class task_class, ?Array[Attribute] attrs) -> void
|
|
91
|
-
def define_readers_on!(task_class, attrs = registry)
|
|
92
|
-
attrs.each { |attr| define_reader_tree!(task_class, attr) }
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Removes eagerly defined reader methods from the task class for
|
|
96
|
-
# attributes about to be deregistered. Must be called before {#deregister}.
|
|
97
|
-
#
|
|
98
|
-
# @param task_class [Class] The task class to undefine readers on
|
|
99
|
-
# @param names [Array<Symbol, String>] Attribute method names being removed
|
|
100
|
-
#
|
|
101
|
-
# @rbs (Class task_class, (Symbol | String | Array[Symbol | String]) names) -> void
|
|
102
|
-
def undefine_readers_on!(task_class, names)
|
|
103
|
-
Utils::Wrap.array(names).each do |name|
|
|
104
|
-
sym = name.to_sym
|
|
105
|
-
|
|
106
|
-
registry.each do |attribute|
|
|
107
|
-
next unless matches_attribute_tree?(attribute, sym)
|
|
108
|
-
|
|
109
|
-
undefine_reader_tree!(task_class, attribute)
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Verifies attribute definitions against a task instance.
|
|
115
|
-
# Each attribute is duped before binding so the class-level originals
|
|
116
|
-
# are never mutated — this eliminates the concurrency hazard of
|
|
117
|
-
# shared mutable @task, @source, and @method_name state.
|
|
118
|
-
#
|
|
119
|
-
# @param task [Task] The task to verify attributes against
|
|
120
|
-
#
|
|
121
|
-
# @rbs (Task task) -> void
|
|
122
|
-
def define_and_verify(task)
|
|
123
|
-
registry.each do |attribute|
|
|
124
|
-
duplicate = attribute.dup
|
|
125
|
-
duplicate.task = task
|
|
126
|
-
duplicate.define_and_verify_tree
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
private
|
|
131
|
-
|
|
132
|
-
# Recursively defines reader methods for an attribute and its children
|
|
133
|
-
# when the method name is statically resolvable.
|
|
134
|
-
#
|
|
135
|
-
# @param task_class [Class] The task class to define readers on
|
|
136
|
-
# @param attribute [Attribute] The attribute to define a reader for
|
|
137
|
-
#
|
|
138
|
-
# @rbs (Class task_class, Attribute attribute) -> void
|
|
139
|
-
def define_reader_tree!(task_class, attribute)
|
|
140
|
-
name = attribute.allocation_name
|
|
141
|
-
|
|
142
|
-
if name && !task_class.method_defined?(name)
|
|
143
|
-
if task_class.private_method_defined?(name)
|
|
144
|
-
raise <<~MESSAGE
|
|
145
|
-
The method #{name.inspect} is already defined on the #{task_class.name} task.
|
|
146
|
-
This may be due conflicts with one of the task's user defined or internal methods/attributes.
|
|
147
|
-
|
|
148
|
-
Use :as, :prefix, and/or :suffix attribute options to avoid conflicts with existing methods.
|
|
149
|
-
MESSAGE
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
task_class.define_method(name) { attributes[name] }
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
attribute.children.each { |child| define_reader_tree!(task_class, child) }
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# Recursively removes reader methods for an attribute and its children.
|
|
159
|
-
#
|
|
160
|
-
# @param task_class [Class] The task class to undefine readers on
|
|
161
|
-
# @param attribute [Attribute] The attribute whose readers to remove
|
|
162
|
-
#
|
|
163
|
-
# @rbs (Class task_class, Attribute attribute) -> void
|
|
164
|
-
def undefine_reader_tree!(task_class, attribute)
|
|
165
|
-
name = attribute.allocation_name
|
|
166
|
-
task_class.remove_method(name) if name && task_class.method_defined?(name, false)
|
|
167
|
-
attribute.children.each { |child| undefine_reader_tree!(task_class, child) }
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
# Recursively checks if an attribute or any of its children match the given name.
|
|
171
|
-
#
|
|
172
|
-
# @param attribute [Attribute] The attribute to check
|
|
173
|
-
# @param name [Symbol] The name to match against
|
|
174
|
-
#
|
|
175
|
-
# @return [Boolean] True if the attribute or any child matches the name
|
|
176
|
-
#
|
|
177
|
-
# @rbs (Attribute attribute, Symbol name) -> bool
|
|
178
|
-
def matches_attribute_tree?(attribute, name)
|
|
179
|
-
return true if attribute.method_name == name
|
|
180
|
-
|
|
181
|
-
attribute.children.any? { |child| matches_attribute_tree?(child, name) }
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
end
|
|
185
|
-
end
|
data/lib/cmdx/attribute_value.rb
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module CMDx
|
|
4
|
-
# Manages the value lifecycle for a single attribute within a task.
|
|
5
|
-
# Handles value sourcing, derivation, coercion, and validation through
|
|
6
|
-
# a coordinated pipeline that ensures data integrity and type safety.
|
|
7
|
-
class AttributeValue
|
|
8
|
-
|
|
9
|
-
extend Forwardable
|
|
10
|
-
|
|
11
|
-
# Returns the attribute managed by this value handler.
|
|
12
|
-
#
|
|
13
|
-
# @return [Attribute] The attribute instance
|
|
14
|
-
#
|
|
15
|
-
# @example
|
|
16
|
-
# attr_value.attribute.name # => :user_id
|
|
17
|
-
#
|
|
18
|
-
# @rbs @attribute: Attribute
|
|
19
|
-
attr_reader :attribute
|
|
20
|
-
|
|
21
|
-
def_delegators :attribute, :task, :parent, :name, :options, :types, :source, :method_name, :required?, :optional?
|
|
22
|
-
def_delegators :task, :attributes, :errors
|
|
23
|
-
|
|
24
|
-
# Creates a new attribute value manager for the given attribute.
|
|
25
|
-
#
|
|
26
|
-
# @param attribute [Attribute] The attribute to manage values for
|
|
27
|
-
#
|
|
28
|
-
# @example
|
|
29
|
-
# attr = Attribute.new(:user_id, required: true)
|
|
30
|
-
# attr_value = AttributeValue.new(attr)
|
|
31
|
-
#
|
|
32
|
-
# @rbs (Attribute attribute) -> void
|
|
33
|
-
def initialize(attribute)
|
|
34
|
-
@attribute = attribute
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Retrieves the current value for this attribute from the task's attributes.
|
|
38
|
-
#
|
|
39
|
-
# @return [Object, nil] The current attribute value or nil if not set
|
|
40
|
-
#
|
|
41
|
-
# @example
|
|
42
|
-
# attr_value.value # => "john_doe"
|
|
43
|
-
#
|
|
44
|
-
# @rbs () -> untyped
|
|
45
|
-
def value
|
|
46
|
-
attributes[method_name]
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Generates the attribute value through the complete pipeline:
|
|
50
|
-
# sourcing, derivation, coercion, and storage.
|
|
51
|
-
#
|
|
52
|
-
# @return [Object, nil] The generated value or nil if generation failed
|
|
53
|
-
#
|
|
54
|
-
# @example
|
|
55
|
-
# attr_value.generate # => 42
|
|
56
|
-
#
|
|
57
|
-
# @rbs () -> untyped
|
|
58
|
-
def generate
|
|
59
|
-
return value if attributes.key?(method_name)
|
|
60
|
-
|
|
61
|
-
sourced_value = source_value
|
|
62
|
-
return if errors.for?(method_name)
|
|
63
|
-
|
|
64
|
-
derived_value = derive_value(sourced_value)
|
|
65
|
-
return if errors.for?(method_name)
|
|
66
|
-
|
|
67
|
-
coerced_value = coerce_value(derived_value)
|
|
68
|
-
transformed_value = transform_value(coerced_value)
|
|
69
|
-
return if errors.for?(method_name)
|
|
70
|
-
|
|
71
|
-
attributes[method_name] = transformed_value
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Validates the current attribute value against configured validators.
|
|
75
|
-
#
|
|
76
|
-
# @raise [ValidationError] When validation fails (handled internally)
|
|
77
|
-
#
|
|
78
|
-
# @example
|
|
79
|
-
# attr_value.validate
|
|
80
|
-
# # Validates value against :presence, :format, etc.
|
|
81
|
-
#
|
|
82
|
-
# @rbs () -> void
|
|
83
|
-
def validate
|
|
84
|
-
registry = task.class.settings.validators
|
|
85
|
-
|
|
86
|
-
options.slice(*registry.keys).each do |type, opts|
|
|
87
|
-
registry.validate(type, task, value, opts)
|
|
88
|
-
rescue ValidationError => e
|
|
89
|
-
errors.add(method_name, e.message)
|
|
90
|
-
nil
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
private
|
|
95
|
-
|
|
96
|
-
# Retrieves the source value for this attribute from various sources.
|
|
97
|
-
#
|
|
98
|
-
# @return [Object, nil] The sourced value or nil if unavailable
|
|
99
|
-
#
|
|
100
|
-
# @raise [NoMethodError] When the source method doesn't exist
|
|
101
|
-
#
|
|
102
|
-
# @example
|
|
103
|
-
# # Sources from task method, proc, or direct value
|
|
104
|
-
# source_value # => "raw_value"
|
|
105
|
-
# @rbs () -> untyped
|
|
106
|
-
def source_value
|
|
107
|
-
sourced_value =
|
|
108
|
-
case source
|
|
109
|
-
when Symbol then task.send(source)
|
|
110
|
-
when Proc then task.instance_eval(&source)
|
|
111
|
-
else source.respond_to?(:call) ? source.call(task) : source
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
if required? && (parent.nil? || parent&.required?) && Utils::Condition.evaluate(task, options)
|
|
115
|
-
case sourced_value
|
|
116
|
-
when Context then sourced_value.key?(name)
|
|
117
|
-
when Hash then sourced_value.key?(name.to_s) || sourced_value.key?(name.to_sym)
|
|
118
|
-
when Proc then true # HACK: Cannot be determined so assume it will respond
|
|
119
|
-
else sourced_value.respond_to?(name, true)
|
|
120
|
-
end || errors.add(method_name, Locale.t("cmdx.attributes.required", method: source))
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
sourced_value
|
|
124
|
-
rescue NoMethodError
|
|
125
|
-
errors.add(method_name, Locale.t("cmdx.attributes.undefined", method: source))
|
|
126
|
-
nil
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
# Retrieves the default value for this attribute if configured.
|
|
130
|
-
#
|
|
131
|
-
# @return [Object, nil] The default value or nil if not configured
|
|
132
|
-
#
|
|
133
|
-
# @example
|
|
134
|
-
# # Default can be symbol, proc, or direct value
|
|
135
|
-
# -> { rand(100) } # => 23
|
|
136
|
-
#
|
|
137
|
-
# @rbs () -> untyped
|
|
138
|
-
def default_value
|
|
139
|
-
default = options[:default]
|
|
140
|
-
|
|
141
|
-
if default.is_a?(Symbol) && task.respond_to?(default, true)
|
|
142
|
-
task.send(default)
|
|
143
|
-
elsif default.is_a?(Proc)
|
|
144
|
-
task.instance_eval(&default)
|
|
145
|
-
elsif default.respond_to?(:call)
|
|
146
|
-
default.call(task)
|
|
147
|
-
else
|
|
148
|
-
default
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Derives the actual value from the source value using various strategies.
|
|
153
|
-
#
|
|
154
|
-
# @param source_value [Object] The source value to derive from
|
|
155
|
-
#
|
|
156
|
-
# @return [Object, nil] The derived value or nil if derivation failed
|
|
157
|
-
#
|
|
158
|
-
# @raise [NoMethodError] When the derivation method doesn't exist
|
|
159
|
-
#
|
|
160
|
-
# @example
|
|
161
|
-
# # Derives from hash key, method call, or proc execution
|
|
162
|
-
# context.user_id # => 42
|
|
163
|
-
#
|
|
164
|
-
# @rbs (untyped source_value) -> untyped
|
|
165
|
-
def derive_value(source_value)
|
|
166
|
-
derived_value =
|
|
167
|
-
case source_value
|
|
168
|
-
when Context then source_value[name]
|
|
169
|
-
when Hash
|
|
170
|
-
if source_value.key?(name.to_s)
|
|
171
|
-
source_value[name.to_s]
|
|
172
|
-
elsif source_value.key?(name.to_sym)
|
|
173
|
-
source_value[name.to_sym]
|
|
174
|
-
end
|
|
175
|
-
when Symbol then source_value.send(name)
|
|
176
|
-
when Proc then task.instance_exec(name, &source_value)
|
|
177
|
-
else
|
|
178
|
-
if source_value.respond_to?(:call)
|
|
179
|
-
source_value.call(task, name)
|
|
180
|
-
elsif source_value.respond_to?(name, true)
|
|
181
|
-
source_value.send(name)
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
derived_value.nil? ? default_value : derived_value
|
|
186
|
-
rescue NoMethodError
|
|
187
|
-
errors.add(method_name, Locale.t("cmdx.attributes.undefined", method: name))
|
|
188
|
-
nil
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# Transforms the derived value using the transform option.
|
|
192
|
-
#
|
|
193
|
-
# @param derived_value [Object] The value to transform
|
|
194
|
-
#
|
|
195
|
-
# @return [Object, nil] The transformed value or nil if transformation failed
|
|
196
|
-
#
|
|
197
|
-
# @example
|
|
198
|
-
# :downcase # => "hello"
|
|
199
|
-
#
|
|
200
|
-
# @rbs (untyped derived_value) -> untyped
|
|
201
|
-
def transform_value(derived_value)
|
|
202
|
-
transform = options[:transform]
|
|
203
|
-
|
|
204
|
-
if transform.is_a?(Symbol) && derived_value.respond_to?(transform, true)
|
|
205
|
-
derived_value.send(transform)
|
|
206
|
-
elsif transform.respond_to?(:call)
|
|
207
|
-
transform.call(derived_value)
|
|
208
|
-
else
|
|
209
|
-
derived_value
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
# Coerces the derived value to the expected type(s) using the coercion registry.
|
|
214
|
-
#
|
|
215
|
-
# @param transformed_value [Object] The value to coerce
|
|
216
|
-
#
|
|
217
|
-
# @return [Object, nil] The coerced value or nil if coercion failed
|
|
218
|
-
#
|
|
219
|
-
# @raise [CoercionError] When coercion fails (handled internally)
|
|
220
|
-
#
|
|
221
|
-
# @example
|
|
222
|
-
# # Coerces "42" to Integer, "true" to Boolean, etc.
|
|
223
|
-
# coerce_value("42") # => 42
|
|
224
|
-
#
|
|
225
|
-
# @rbs (untyped transformed_value) -> untyped
|
|
226
|
-
def coerce_value(transformed_value)
|
|
227
|
-
return transformed_value if types.empty?
|
|
228
|
-
|
|
229
|
-
registry = task.class.settings.coercions
|
|
230
|
-
last_idx = types.size - 1
|
|
231
|
-
|
|
232
|
-
types.find.with_index do |type, i|
|
|
233
|
-
break registry.coerce(type, task, transformed_value, options)
|
|
234
|
-
rescue CoercionError => e
|
|
235
|
-
next if i != last_idx
|
|
236
|
-
|
|
237
|
-
unless optional? && transformed_value.nil?
|
|
238
|
-
message =
|
|
239
|
-
if last_idx.zero?
|
|
240
|
-
e.message
|
|
241
|
-
else
|
|
242
|
-
tl = types.map { |t| Locale.t("cmdx.types.#{t}") }.join(", ")
|
|
243
|
-
Locale.t("cmdx.coercions.into_any", types: tl)
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
errors.add(method_name, message)
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
end
|
|
252
|
-
end
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module CMDx
|
|
4
|
-
# Registry for managing callbacks that can be executed at various points during task execution.
|
|
5
|
-
#
|
|
6
|
-
# Callbacks are organized by type and can be registered with optional conditions and options.
|
|
7
|
-
# Each callback type represents a specific execution phase or outcome.
|
|
8
|
-
#
|
|
9
|
-
# Supports copy-on-write semantics: a duped registry shares the parent's
|
|
10
|
-
# data until a write operation triggers materialization.
|
|
11
|
-
class CallbackRegistry
|
|
12
|
-
|
|
13
|
-
extend Forwardable
|
|
14
|
-
|
|
15
|
-
# @rbs TYPES: Array[Symbol]
|
|
16
|
-
TYPES = %i[
|
|
17
|
-
before_validation
|
|
18
|
-
before_execution
|
|
19
|
-
on_complete
|
|
20
|
-
on_interrupted
|
|
21
|
-
on_executed
|
|
22
|
-
on_success
|
|
23
|
-
on_skipped
|
|
24
|
-
on_failed
|
|
25
|
-
on_good
|
|
26
|
-
on_bad
|
|
27
|
-
].freeze
|
|
28
|
-
|
|
29
|
-
# @rbs TYPES_SET: Set[Symbol]
|
|
30
|
-
TYPES_SET = TYPES.to_set.freeze
|
|
31
|
-
private_constant :TYPES_SET
|
|
32
|
-
|
|
33
|
-
def_delegators :registry, :empty?
|
|
34
|
-
|
|
35
|
-
# @param registry [Hash, nil] Initial registry hash, defaults to empty
|
|
36
|
-
#
|
|
37
|
-
# @rbs (?Hash[Symbol, Set[Array[untyped]]]? registry) -> void
|
|
38
|
-
def initialize(registry = nil)
|
|
39
|
-
@registry = registry || {}
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Sets up copy-on-write state when duplicated via dup.
|
|
43
|
-
#
|
|
44
|
-
# @param source [CallbackRegistry] The registry being duplicated
|
|
45
|
-
#
|
|
46
|
-
# @rbs (CallbackRegistry source) -> void
|
|
47
|
-
def initialize_dup(source)
|
|
48
|
-
@parent = source
|
|
49
|
-
@registry = nil
|
|
50
|
-
super
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Returns the internal registry of callbacks organized by type.
|
|
54
|
-
# Delegates to the parent registry when not yet materialized.
|
|
55
|
-
#
|
|
56
|
-
# @return [Hash{Symbol => Set<Array>}] Hash mapping callback types to their registered callables
|
|
57
|
-
#
|
|
58
|
-
# @example
|
|
59
|
-
# registry.registry # => { before_execution: #<Set: [[[:validate], {}]]> }
|
|
60
|
-
#
|
|
61
|
-
# @rbs () -> Hash[Symbol, Set[Array[untyped]]]
|
|
62
|
-
def registry
|
|
63
|
-
@registry || @parent.registry
|
|
64
|
-
end
|
|
65
|
-
alias to_h registry
|
|
66
|
-
|
|
67
|
-
# Registers one or more callables for a specific callback type
|
|
68
|
-
#
|
|
69
|
-
# @param type [Symbol] The callback type from TYPES
|
|
70
|
-
# @param callables [Array<#call>] Callable objects to register
|
|
71
|
-
# @param options [Hash] Options to pass to the callback
|
|
72
|
-
# @param block [Proc] Optional block to register as a callable
|
|
73
|
-
# @option options [Hash] :if Condition hash for conditional execution
|
|
74
|
-
# @option options [Hash] :unless Inverse condition hash for conditional execution
|
|
75
|
-
#
|
|
76
|
-
# @return [CallbackRegistry] self for method chaining
|
|
77
|
-
#
|
|
78
|
-
# @raise [ArgumentError] When type is not a valid callback type
|
|
79
|
-
#
|
|
80
|
-
# @example Register a method callback
|
|
81
|
-
# registry.register(:before_execution, :validate_inputs)
|
|
82
|
-
# @example Register a block with conditions
|
|
83
|
-
# registry.register(:on_success, if: { status: :completed }) do |task|
|
|
84
|
-
# task.log("Success callback executed")
|
|
85
|
-
# end
|
|
86
|
-
#
|
|
87
|
-
# @rbs (Symbol type, *untyped callables, **untyped options) ?{ (Task) -> void } -> self
|
|
88
|
-
def register(type, *callables, **options, &block)
|
|
89
|
-
materialize!
|
|
90
|
-
|
|
91
|
-
callables << block if block_given?
|
|
92
|
-
|
|
93
|
-
@registry[type] ||= Set.new
|
|
94
|
-
@registry[type] << [callables, options]
|
|
95
|
-
self
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Removes one or more callables for a specific callback type
|
|
99
|
-
#
|
|
100
|
-
# @param type [Symbol] The callback type from TYPES
|
|
101
|
-
# @param callables [Array<#call>] Callable objects to remove
|
|
102
|
-
# @param options [Hash] Options that were used during registration
|
|
103
|
-
# @param block [Proc] Optional block to remove
|
|
104
|
-
# @option options [Object] :* Any option key-value pairs
|
|
105
|
-
#
|
|
106
|
-
# @return [CallbackRegistry] self for method chaining
|
|
107
|
-
#
|
|
108
|
-
# @example Remove a specific callback
|
|
109
|
-
# registry.deregister(:before_execution, :validate_inputs)
|
|
110
|
-
#
|
|
111
|
-
# @rbs (Symbol type, *untyped callables, **untyped options) ?{ (Task) -> void } -> self
|
|
112
|
-
def deregister(type, *callables, **options, &block)
|
|
113
|
-
materialize!
|
|
114
|
-
|
|
115
|
-
callables << block if block_given?
|
|
116
|
-
return self unless @registry[type]
|
|
117
|
-
|
|
118
|
-
@registry[type].delete([callables, options])
|
|
119
|
-
@registry.delete(type) if @registry[type].empty?
|
|
120
|
-
self
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Invokes all registered callbacks for a given type
|
|
124
|
-
#
|
|
125
|
-
# @param type [Symbol] The callback type to invoke
|
|
126
|
-
# @param task [Task] The task instance to pass to callbacks
|
|
127
|
-
#
|
|
128
|
-
# @raise [TypeError] When type is not a valid callback type
|
|
129
|
-
#
|
|
130
|
-
# @example Invoke all before_execution callbacks
|
|
131
|
-
# registry.invoke(:before_execution, task)
|
|
132
|
-
#
|
|
133
|
-
# @rbs (Symbol type, Task task) -> void
|
|
134
|
-
def invoke(type, task)
|
|
135
|
-
raise TypeError, "unknown callback type #{type.inspect}" unless TYPES_SET.include?(type)
|
|
136
|
-
return unless registry[type]
|
|
137
|
-
|
|
138
|
-
registry[type].each do |callables, options|
|
|
139
|
-
next unless Utils::Condition.evaluate(task, options)
|
|
140
|
-
|
|
141
|
-
Utils::Wrap.array(callables).each do |callable|
|
|
142
|
-
if callable.is_a?(Symbol)
|
|
143
|
-
task.send(callable)
|
|
144
|
-
elsif callable.is_a?(Proc)
|
|
145
|
-
task.instance_exec(&callable)
|
|
146
|
-
elsif callable.respond_to?(:call)
|
|
147
|
-
callable.call(task)
|
|
148
|
-
else
|
|
149
|
-
raise "cannot invoke #{callable}"
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
private
|
|
156
|
-
|
|
157
|
-
# Copies the parent's registry data into this instance,
|
|
158
|
-
# severing the copy-on-write link.
|
|
159
|
-
#
|
|
160
|
-
# @rbs () -> void
|
|
161
|
-
def materialize!
|
|
162
|
-
return if @registry
|
|
163
|
-
|
|
164
|
-
@registry = @parent.registry.transform_values(&:dup)
|
|
165
|
-
@parent = nil
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
end
|
|
169
|
-
end
|