cmdx 1.19.0 → 1.21.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/.DS_Store +0 -0
- data/CHANGELOG.md +82 -16
- data/README.md +1 -1
- data/lib/cmdx/attribute.rb +82 -19
- data/lib/cmdx/attribute_registry.rb +79 -8
- data/lib/cmdx/attribute_value.rb +2 -2
- data/lib/cmdx/callback_registry.rb +60 -26
- data/lib/cmdx/chain.rb +34 -5
- data/lib/cmdx/coercion_registry.rb +42 -20
- data/lib/cmdx/coercions/array.rb +2 -2
- data/lib/cmdx/coercions/big_decimal.rb +1 -1
- data/lib/cmdx/coercions/boolean.rb +2 -2
- data/lib/cmdx/coercions/complex.rb +1 -1
- data/lib/cmdx/coercions/date.rb +1 -1
- data/lib/cmdx/coercions/date_time.rb +1 -1
- data/lib/cmdx/coercions/float.rb +1 -1
- data/lib/cmdx/coercions/hash.rb +1 -1
- data/lib/cmdx/coercions/integer.rb +1 -1
- data/lib/cmdx/coercions/rational.rb +1 -1
- data/lib/cmdx/coercions/string.rb +1 -1
- data/lib/cmdx/coercions/symbol.rb +1 -1
- data/lib/cmdx/coercions/time.rb +1 -1
- data/lib/cmdx/configuration.rb +38 -0
- data/lib/cmdx/context.rb +11 -8
- data/lib/cmdx/deprecator.rb +27 -14
- data/lib/cmdx/errors.rb +3 -4
- data/lib/cmdx/exception.rb +7 -0
- data/lib/cmdx/executor.rb +80 -53
- data/lib/cmdx/identifier.rb +4 -6
- data/lib/cmdx/locale.rb +32 -9
- data/lib/cmdx/middleware_registry.rb +43 -23
- data/lib/cmdx/middlewares/correlate.rb +4 -2
- data/lib/cmdx/middlewares/runtime.rb +18 -3
- data/lib/cmdx/middlewares/timeout.rb +11 -10
- data/lib/cmdx/parallelizer.rb +100 -0
- data/lib/cmdx/pipeline.rb +42 -23
- data/lib/cmdx/railtie.rb +1 -1
- data/lib/cmdx/result.rb +91 -19
- data/lib/cmdx/retry.rb +166 -0
- data/lib/cmdx/settings.rb +226 -0
- data/lib/cmdx/task.rb +62 -65
- data/lib/cmdx/utils/format.rb +17 -1
- data/lib/cmdx/utils/normalize.rb +52 -0
- data/lib/cmdx/utils/wrap.rb +38 -0
- data/lib/cmdx/validator_registry.rb +44 -19
- data/lib/cmdx/validators/absence.rb +1 -1
- data/lib/cmdx/validators/exclusion.rb +2 -2
- data/lib/cmdx/validators/format.rb +1 -1
- data/lib/cmdx/validators/inclusion.rb +2 -2
- data/lib/cmdx/validators/length.rb +1 -1
- data/lib/cmdx/validators/numeric.rb +1 -1
- data/lib/cmdx/validators/presence.rb +1 -1
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/workflow.rb +17 -0
- data/lib/cmdx.rb +12 -0
- data/lib/generators/cmdx/templates/install.rb +20 -5
- data/lib/locales/af.yml +2 -0
- data/lib/locales/ar.yml +2 -0
- data/lib/locales/az.yml +2 -0
- data/lib/locales/be.yml +2 -0
- data/lib/locales/bg.yml +2 -0
- data/lib/locales/bn.yml +2 -0
- data/lib/locales/bs.yml +2 -0
- data/lib/locales/ca.yml +2 -0
- data/lib/locales/cnr.yml +2 -0
- data/lib/locales/cs.yml +2 -0
- data/lib/locales/cy.yml +2 -0
- data/lib/locales/da.yml +2 -0
- data/lib/locales/de.yml +2 -0
- data/lib/locales/dz.yml +2 -0
- data/lib/locales/el.yml +2 -0
- data/lib/locales/en.yml +2 -0
- data/lib/locales/eo.yml +2 -0
- data/lib/locales/es.yml +2 -0
- data/lib/locales/et.yml +2 -0
- data/lib/locales/eu.yml +2 -0
- data/lib/locales/fa.yml +2 -0
- data/lib/locales/fi.yml +2 -0
- data/lib/locales/fr.yml +2 -0
- data/lib/locales/fy.yml +2 -0
- data/lib/locales/gd.yml +2 -0
- data/lib/locales/gl.yml +2 -0
- data/lib/locales/he.yml +2 -0
- data/lib/locales/hi.yml +2 -0
- data/lib/locales/hr.yml +2 -0
- data/lib/locales/hu.yml +2 -0
- data/lib/locales/hy.yml +2 -0
- data/lib/locales/id.yml +2 -0
- data/lib/locales/is.yml +2 -0
- data/lib/locales/it.yml +2 -0
- data/lib/locales/ja.yml +2 -0
- data/lib/locales/ka.yml +2 -0
- data/lib/locales/kk.yml +2 -0
- data/lib/locales/km.yml +2 -0
- data/lib/locales/kn.yml +2 -0
- data/lib/locales/ko.yml +2 -0
- data/lib/locales/lb.yml +2 -0
- data/lib/locales/lo.yml +2 -0
- data/lib/locales/lt.yml +2 -0
- data/lib/locales/lv.yml +2 -0
- data/lib/locales/mg.yml +2 -0
- data/lib/locales/mk.yml +2 -0
- data/lib/locales/ml.yml +2 -0
- data/lib/locales/mn.yml +2 -0
- data/lib/locales/mr-IN.yml +2 -0
- data/lib/locales/ms.yml +2 -0
- data/lib/locales/nb.yml +2 -0
- data/lib/locales/ne.yml +2 -0
- data/lib/locales/nl.yml +2 -0
- data/lib/locales/nn.yml +2 -0
- data/lib/locales/oc.yml +2 -0
- data/lib/locales/or.yml +2 -0
- data/lib/locales/pa.yml +2 -0
- data/lib/locales/pl.yml +2 -0
- data/lib/locales/pt.yml +2 -0
- data/lib/locales/rm.yml +2 -0
- data/lib/locales/ro.yml +2 -0
- data/lib/locales/ru.yml +2 -0
- data/lib/locales/sc.yml +2 -0
- data/lib/locales/sk.yml +2 -0
- data/lib/locales/sl.yml +2 -0
- data/lib/locales/sq.yml +2 -0
- data/lib/locales/sr.yml +2 -0
- data/lib/locales/st.yml +2 -0
- data/lib/locales/sv.yml +2 -0
- data/lib/locales/sw.yml +2 -0
- data/lib/locales/ta.yml +2 -0
- data/lib/locales/te.yml +2 -0
- data/lib/locales/th.yml +2 -0
- data/lib/locales/tl.yml +2 -0
- data/lib/locales/tr.yml +2 -0
- data/lib/locales/tt.yml +2 -0
- data/lib/locales/ug.yml +2 -0
- data/lib/locales/uk.yml +2 -0
- data/lib/locales/ur.yml +2 -0
- data/lib/locales/uz.yml +2 -0
- data/lib/locales/vi.yml +2 -0
- data/lib/locales/wo.yml +2 -0
- data/lib/locales/zh-CN.yml +2 -0
- data/lib/locales/zh-HK.yml +2 -0
- data/lib/locales/zh-TW.yml +2 -0
- data/lib/locales/zh-YUE.yml +2 -0
- data/mkdocs.yml +5 -1
- metadata +6 -15
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CMDx
|
|
4
|
+
# Value object encapsulating all per-task configuration. Registries are
|
|
5
|
+
# deep-duped on inheritance; scalar settings delegate to a parent Settings
|
|
6
|
+
# or to the global Configuration rather than eagerly copying values.
|
|
7
|
+
class Settings
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Defines a reader that delegates to the parent Settings chain,
|
|
14
|
+
# falling through to Configuration when no parent exists.
|
|
15
|
+
#
|
|
16
|
+
# @param names [Array<Symbol>] Setting names to define
|
|
17
|
+
#
|
|
18
|
+
# @rbs (*Symbol names) -> void
|
|
19
|
+
def delegate_to_configuration(*names)
|
|
20
|
+
names.each do |name|
|
|
21
|
+
ivar = :"@#{name}"
|
|
22
|
+
|
|
23
|
+
attr_writer(name)
|
|
24
|
+
|
|
25
|
+
define_method(name) do
|
|
26
|
+
return instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
|
27
|
+
|
|
28
|
+
value = @parent ? @parent.public_send(name) : CMDx.configuration.public_send(name)
|
|
29
|
+
instance_variable_set(ivar, value)
|
|
30
|
+
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Defines a reader that delegates to the parent Settings only.
|
|
37
|
+
# Returns nil when the chain is exhausted.
|
|
38
|
+
#
|
|
39
|
+
# @param names [Array<Symbol>] Setting names to define
|
|
40
|
+
# @param with_fallback [Boolean] Whether to fall back to Configuration
|
|
41
|
+
#
|
|
42
|
+
# @rbs (*Symbol names, with_fallback: bool) -> void
|
|
43
|
+
def delegate_to_parent(*names, with_fallback: false)
|
|
44
|
+
names.each do |name|
|
|
45
|
+
ivar = :"@#{name}"
|
|
46
|
+
|
|
47
|
+
attr_writer(name)
|
|
48
|
+
|
|
49
|
+
define_method(name) do
|
|
50
|
+
return instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
|
51
|
+
|
|
52
|
+
value = @parent&.public_send(name)
|
|
53
|
+
value ||= CMDx.configuration.public_send(name) if with_fallback
|
|
54
|
+
instance_variable_set(ivar, value)
|
|
55
|
+
|
|
56
|
+
value
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns the attribute registry for task parameters.
|
|
64
|
+
#
|
|
65
|
+
# @return [AttributeRegistry] The attribute registry
|
|
66
|
+
#
|
|
67
|
+
# @rbs @attributes: AttributeRegistry
|
|
68
|
+
attr_accessor :attributes
|
|
69
|
+
|
|
70
|
+
# Returns the callback registry for task lifecycle hooks.
|
|
71
|
+
#
|
|
72
|
+
# @return [CallbackRegistry] The callback registry
|
|
73
|
+
#
|
|
74
|
+
# @rbs @callbacks: CallbackRegistry
|
|
75
|
+
attr_accessor :callbacks
|
|
76
|
+
|
|
77
|
+
# Returns the coercion registry for type conversions.
|
|
78
|
+
#
|
|
79
|
+
# @return [CoercionRegistry] The coercion registry
|
|
80
|
+
#
|
|
81
|
+
# @rbs @coercions: CoercionRegistry
|
|
82
|
+
attr_accessor :coercions
|
|
83
|
+
|
|
84
|
+
# Returns the middleware registry for task execution.
|
|
85
|
+
#
|
|
86
|
+
# @return [MiddlewareRegistry] The middleware registry
|
|
87
|
+
#
|
|
88
|
+
# @rbs @middlewares: MiddlewareRegistry
|
|
89
|
+
attr_accessor :middlewares
|
|
90
|
+
|
|
91
|
+
# Returns the validator registry for attribute validation.
|
|
92
|
+
#
|
|
93
|
+
# @return [ValidatorRegistry] The validator registry
|
|
94
|
+
#
|
|
95
|
+
# @rbs @validators: ValidatorRegistry
|
|
96
|
+
attr_accessor :validators
|
|
97
|
+
|
|
98
|
+
# Returns the expected return keys after execution.
|
|
99
|
+
#
|
|
100
|
+
# @return [Array<Symbol>] Expected return keys after execution
|
|
101
|
+
#
|
|
102
|
+
# @rbs @returns: Array[Symbol]
|
|
103
|
+
attr_accessor :returns
|
|
104
|
+
|
|
105
|
+
# Returns the tags for task categorization.
|
|
106
|
+
#
|
|
107
|
+
# @return [Array<Symbol>] Tags for categorization
|
|
108
|
+
#
|
|
109
|
+
# @rbs @tags: Array[Symbol]
|
|
110
|
+
attr_accessor :tags
|
|
111
|
+
|
|
112
|
+
# @!attribute [rw] backtrace
|
|
113
|
+
# @return [Boolean] true if backtraces should be logged
|
|
114
|
+
delegate_to_configuration :backtrace
|
|
115
|
+
|
|
116
|
+
# @!attribute [rw] dump_context
|
|
117
|
+
# @return [Boolean] true if context should be included in Task#to_h
|
|
118
|
+
delegate_to_configuration :dump_context
|
|
119
|
+
|
|
120
|
+
# @!attribute [rw] rollback_on
|
|
121
|
+
# @return [Array<String>] Statuses that trigger rollback
|
|
122
|
+
delegate_to_configuration :rollback_on
|
|
123
|
+
|
|
124
|
+
# @!attribute [rw] task_breakpoints
|
|
125
|
+
# @return [Array<String>] Default task breakpoint statuses
|
|
126
|
+
delegate_to_configuration :task_breakpoints
|
|
127
|
+
|
|
128
|
+
# @!attribute [rw] workflow_breakpoints
|
|
129
|
+
# @return [Array<String>] Default workflow breakpoint statuses
|
|
130
|
+
delegate_to_configuration :workflow_breakpoints
|
|
131
|
+
|
|
132
|
+
# @!attribute [rw] backtrace_cleaner
|
|
133
|
+
# @return [Proc, nil] The backtrace cleaner proc
|
|
134
|
+
delegate_to_parent :backtrace_cleaner, with_fallback: true
|
|
135
|
+
|
|
136
|
+
# @!attribute [rw] breakpoints
|
|
137
|
+
# @return [Array<String>, nil] Per-task breakpoints override
|
|
138
|
+
delegate_to_parent :breakpoints
|
|
139
|
+
|
|
140
|
+
# @!attribute [rw] deprecate
|
|
141
|
+
# @return [Symbol, Proc, Boolean, nil] Deprecation behavior
|
|
142
|
+
delegate_to_parent :deprecate
|
|
143
|
+
|
|
144
|
+
# @!attribute [rw] exception_handler
|
|
145
|
+
# @return [Proc, nil] The exception handler proc
|
|
146
|
+
delegate_to_parent :exception_handler, with_fallback: true
|
|
147
|
+
|
|
148
|
+
# @!attribute [rw] logger
|
|
149
|
+
# @return [Logger] The logger instance
|
|
150
|
+
delegate_to_parent :logger, with_fallback: true
|
|
151
|
+
|
|
152
|
+
# @!attribute [rw] log_formatter
|
|
153
|
+
# @return [Proc, nil] Per-task log formatter override
|
|
154
|
+
delegate_to_parent :log_formatter
|
|
155
|
+
|
|
156
|
+
# @!attribute [rw] log_level
|
|
157
|
+
# @return [Integer, nil] Per-task log level override
|
|
158
|
+
delegate_to_parent :log_level
|
|
159
|
+
|
|
160
|
+
# @!attribute [rw] retries
|
|
161
|
+
# @return [Integer, nil] Number of retries on failure
|
|
162
|
+
delegate_to_parent :retries
|
|
163
|
+
|
|
164
|
+
# @!attribute [rw] retry_jitter
|
|
165
|
+
# @return [Numeric, Symbol, Proc, nil] Jitter between retries
|
|
166
|
+
delegate_to_parent :retry_jitter
|
|
167
|
+
|
|
168
|
+
# @!attribute [rw] retry_on
|
|
169
|
+
# @return [Array<Class>, Class, nil] Exception classes to retry on
|
|
170
|
+
delegate_to_parent :retry_on
|
|
171
|
+
|
|
172
|
+
# Creates a new Settings instance, inheriting registries from a parent
|
|
173
|
+
# Settings or the global Configuration. Scalar settings are resolved
|
|
174
|
+
# lazily via delegation rather than eagerly copied.
|
|
175
|
+
#
|
|
176
|
+
# @param parent [Settings, nil] Parent settings to inherit from
|
|
177
|
+
# @param overrides [Hash] Field values to override after inheritance
|
|
178
|
+
#
|
|
179
|
+
# @example
|
|
180
|
+
# Settings.new(parent: ParentTask.settings, deprecate: true)
|
|
181
|
+
#
|
|
182
|
+
# @rbs (?parent: Settings?, **untyped overrides) -> void
|
|
183
|
+
def initialize(parent: nil, **overrides)
|
|
184
|
+
@parent = parent
|
|
185
|
+
|
|
186
|
+
init_registries
|
|
187
|
+
init_collections
|
|
188
|
+
|
|
189
|
+
overrides.each { |key, value| public_send(:"#{key}=", value) }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
private
|
|
193
|
+
|
|
194
|
+
# Dups registries from the parent Settings or global Configuration
|
|
195
|
+
# so each task class gets its own mutable copy.
|
|
196
|
+
#
|
|
197
|
+
# @rbs () -> void
|
|
198
|
+
def init_registries
|
|
199
|
+
if @parent
|
|
200
|
+
@middlewares = @parent.middlewares.dup
|
|
201
|
+
@callbacks = @parent.callbacks.dup
|
|
202
|
+
@coercions = @parent.coercions.dup
|
|
203
|
+
@validators = @parent.validators.dup
|
|
204
|
+
@attributes = @parent.attributes.dup
|
|
205
|
+
else
|
|
206
|
+
config = CMDx.configuration
|
|
207
|
+
|
|
208
|
+
@middlewares = config.middlewares.dup
|
|
209
|
+
@callbacks = config.callbacks.dup
|
|
210
|
+
@coercions = config.coercions.dup
|
|
211
|
+
@validators = config.validators.dup
|
|
212
|
+
@attributes = AttributeRegistry.new
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Initializes array-valued settings that need their own copy
|
|
217
|
+
# to avoid cross-class mutation.
|
|
218
|
+
#
|
|
219
|
+
# @rbs () -> void
|
|
220
|
+
def init_collections
|
|
221
|
+
@returns = @parent&.returns&.dup || EMPTY_ARRAY
|
|
222
|
+
@tags = @parent&.tags&.dup || EMPTY_ARRAY
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
end
|
|
226
|
+
end
|
data/lib/cmdx/task.rb
CHANGED
|
@@ -70,72 +70,63 @@ module CMDx
|
|
|
70
70
|
# @rbs @chain: Chain
|
|
71
71
|
attr_reader :chain
|
|
72
72
|
|
|
73
|
-
def_delegators :result, :skip!, :fail!, :throw!
|
|
73
|
+
def_delegators :result, :success!, :skip!, :fail!, :throw!
|
|
74
74
|
def_delegators :chain, :dry_run?
|
|
75
75
|
|
|
76
|
-
# @param context [Hash, Context] The initial context for the task
|
|
77
|
-
#
|
|
78
|
-
# @option context [Object] :* Any key-value pairs to initialize the context
|
|
76
|
+
# @param context [Hash, Context, nil] The initial context for the task
|
|
79
77
|
#
|
|
80
78
|
# @return [Task] A new task instance
|
|
81
79
|
#
|
|
82
80
|
# @raise [DeprecationError] If the task class is deprecated
|
|
83
81
|
#
|
|
84
82
|
# @example
|
|
83
|
+
# task = MyTask.new
|
|
85
84
|
# task = MyTask.new(name: "example", priority: :high)
|
|
86
85
|
# task = MyTask.new(Context.build(name: "example"))
|
|
87
86
|
#
|
|
88
87
|
# @rbs (untyped context) -> void
|
|
89
|
-
def initialize(context =
|
|
88
|
+
def initialize(context = nil)
|
|
90
89
|
Deprecator.restrict(self)
|
|
91
90
|
|
|
92
|
-
@attributes = {}
|
|
93
|
-
@errors = Errors.new
|
|
94
|
-
|
|
95
91
|
@id = Identifier.generate
|
|
96
92
|
@context = Context.build(context)
|
|
93
|
+
@errors = Errors.new
|
|
97
94
|
@result = Result.new(self)
|
|
95
|
+
@chain = Chain.build(@result, dry_run: @context.delete(:dry_run))
|
|
98
96
|
|
|
99
|
-
|
|
100
|
-
@chain = Chain.build(@result, dry_run:)
|
|
97
|
+
@attributes = {}
|
|
101
98
|
end
|
|
102
99
|
|
|
103
100
|
class << self
|
|
104
101
|
|
|
105
|
-
#
|
|
106
|
-
#
|
|
102
|
+
# Returns the cached task type string for this class.
|
|
103
|
+
#
|
|
104
|
+
# @return [String] "Workflow" or "Task"
|
|
105
|
+
#
|
|
106
|
+
# @rbs () -> String
|
|
107
|
+
def type
|
|
108
|
+
@type ||= include?(Workflow) ? "Workflow" : "Task"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Returns (and lazily creates) the task-level Settings object.
|
|
112
|
+
# On first access, inherits from the superclass settings or
|
|
113
|
+
# the global Configuration. Optional overrides are applied once.
|
|
114
|
+
#
|
|
115
|
+
# @param overrides [Hash] Configuration overrides applied on first access
|
|
116
|
+
# @option overrides [Object] :* Any configuration override key-value pairs
|
|
107
117
|
#
|
|
108
|
-
# @return [
|
|
118
|
+
# @return [Settings] The settings instance for this task class
|
|
109
119
|
#
|
|
110
120
|
# @example
|
|
111
121
|
# class MyTask < Task
|
|
112
122
|
# settings deprecate: true, tags: [:experimental]
|
|
113
123
|
# end
|
|
114
124
|
#
|
|
115
|
-
# @rbs (**untyped
|
|
116
|
-
def settings(**
|
|
125
|
+
# @rbs (**untyped overrides) -> Settings
|
|
126
|
+
def settings(**overrides)
|
|
117
127
|
@settings ||= begin
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
parent = superclass.settings
|
|
121
|
-
parent
|
|
122
|
-
.except(:backtrace_cleaner, :exception_handler, :logger, :deprecate)
|
|
123
|
-
.transform_values!(&:dup)
|
|
124
|
-
.merge!(
|
|
125
|
-
backtrace_cleaner: parent[:backtrace_cleaner] || CMDx.configuration.backtrace_cleaner,
|
|
126
|
-
exception_handler: parent[:exception_handler] || CMDx.configuration.exception_handler,
|
|
127
|
-
logger: parent[:logger] || CMDx.configuration.logger,
|
|
128
|
-
deprecate: parent[:deprecate]
|
|
129
|
-
)
|
|
130
|
-
else
|
|
131
|
-
CMDx.configuration.to_h
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
hash[:attributes] ||= AttributeRegistry.new
|
|
135
|
-
hash[:returns] ||= []
|
|
136
|
-
hash[:tags] ||= []
|
|
137
|
-
|
|
138
|
-
hash.merge!(options)
|
|
128
|
+
parent = superclass.settings if superclass.respond_to?(:settings)
|
|
129
|
+
Settings.new(parent:, **overrides)
|
|
139
130
|
end
|
|
140
131
|
end
|
|
141
132
|
|
|
@@ -151,11 +142,13 @@ module CMDx
|
|
|
151
142
|
# @rbs (Symbol type, untyped object, *untyped) -> void
|
|
152
143
|
def register(type, object, ...)
|
|
153
144
|
case type
|
|
154
|
-
when :attribute
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
when :
|
|
158
|
-
when :
|
|
145
|
+
when :attribute
|
|
146
|
+
settings.attributes.register(object)
|
|
147
|
+
settings.attributes.define_readers_on!(self, Utils::Wrap.array(object))
|
|
148
|
+
when :callback then settings.callbacks.register(object, ...)
|
|
149
|
+
when :middleware then settings.middlewares.register(object, ...)
|
|
150
|
+
when :validator then settings.validators.register(object, ...)
|
|
151
|
+
when :coercion then settings.coercions.register(object, ...)
|
|
159
152
|
else raise "unknown registry type #{type.inspect}"
|
|
160
153
|
end
|
|
161
154
|
end
|
|
@@ -172,11 +165,13 @@ module CMDx
|
|
|
172
165
|
# @rbs (Symbol type, untyped object, *untyped) -> void
|
|
173
166
|
def deregister(type, object, ...)
|
|
174
167
|
case type
|
|
175
|
-
when :attribute
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
when :
|
|
179
|
-
when :
|
|
168
|
+
when :attribute
|
|
169
|
+
settings.attributes.undefine_readers_on!(self, object)
|
|
170
|
+
settings.attributes.deregister(object)
|
|
171
|
+
when :callback then settings.callbacks.deregister(object, ...)
|
|
172
|
+
when :middleware then settings.middlewares.deregister(object, ...)
|
|
173
|
+
when :validator then settings.validators.deregister(object, ...)
|
|
174
|
+
when :coercion then settings.coercions.deregister(object, ...)
|
|
180
175
|
else raise "unknown registry type #{type.inspect}"
|
|
181
176
|
end
|
|
182
177
|
end
|
|
@@ -231,7 +226,7 @@ module CMDx
|
|
|
231
226
|
#
|
|
232
227
|
# @rbs (*untyped names) -> void
|
|
233
228
|
def returns(*names)
|
|
234
|
-
settings
|
|
229
|
+
settings.returns |= names.map(&:to_sym)
|
|
235
230
|
end
|
|
236
231
|
|
|
237
232
|
# Removes declared returns from the task.
|
|
@@ -243,7 +238,7 @@ module CMDx
|
|
|
243
238
|
#
|
|
244
239
|
# @rbs (*Symbol names) -> void
|
|
245
240
|
def remove_returns(*names)
|
|
246
|
-
settings
|
|
241
|
+
settings.returns -= names.map(&:to_sym)
|
|
247
242
|
end
|
|
248
243
|
alias remove_return remove_returns
|
|
249
244
|
|
|
@@ -261,7 +256,7 @@ module CMDx
|
|
|
261
256
|
#
|
|
262
257
|
# @rbs () -> Hash[Symbol, Hash[Symbol, untyped]]
|
|
263
258
|
def attributes_schema
|
|
264
|
-
|
|
259
|
+
Utils::Wrap.array(settings.attributes).to_h do |attr|
|
|
265
260
|
[attr.method_name, attr.to_h]
|
|
266
261
|
end
|
|
267
262
|
end
|
|
@@ -292,11 +287,8 @@ module CMDx
|
|
|
292
287
|
#
|
|
293
288
|
# @example
|
|
294
289
|
# result = MyTask.execute(name: "example")
|
|
295
|
-
# if result.success?
|
|
296
|
-
# puts "Task completed successfully"
|
|
297
|
-
# end
|
|
298
290
|
#
|
|
299
|
-
# @rbs (*untyped args, **untyped kwargs) ?{ (Result) -> void } -> Result
|
|
291
|
+
# @rbs (*untyped args, dry_run: bool, **untyped kwargs) ?{ (Result) -> void } -> Result
|
|
300
292
|
def execute(*args, **kwargs)
|
|
301
293
|
task = new(*args, **kwargs)
|
|
302
294
|
task.execute(raise: false)
|
|
@@ -313,9 +305,8 @@ module CMDx
|
|
|
313
305
|
#
|
|
314
306
|
# @example
|
|
315
307
|
# result = MyTask.execute!(name: "example")
|
|
316
|
-
# # Will raise an exception if execution fails
|
|
317
308
|
#
|
|
318
|
-
# @rbs (*untyped args, **untyped kwargs) ?{ (Result) -> void } -> Result
|
|
309
|
+
# @rbs (*untyped args, dry_run: bool, **untyped kwargs) ?{ (Result) -> void } -> Result
|
|
319
310
|
def execute!(*args, **kwargs)
|
|
320
311
|
task = new(*args, **kwargs)
|
|
321
312
|
task.execute(raise: true)
|
|
@@ -366,14 +357,13 @@ module CMDx
|
|
|
366
357
|
# @rbs () -> Logger
|
|
367
358
|
def logger
|
|
368
359
|
@logger ||= begin
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
log_formatter = self.class.settings[:log_formatter]
|
|
360
|
+
settings = self.class.settings
|
|
361
|
+
log_instance = settings.logger || CMDx.configuration.logger
|
|
372
362
|
|
|
373
|
-
if log_level || log_formatter
|
|
363
|
+
if settings.log_level || settings.log_formatter
|
|
374
364
|
log_instance = log_instance.dup
|
|
375
|
-
log_instance.level = log_level if log_level
|
|
376
|
-
log_instance.formatter = log_formatter if log_formatter
|
|
365
|
+
log_instance.level = settings.log_level if settings.log_level
|
|
366
|
+
log_instance.formatter = settings.log_formatter if settings.log_formatter
|
|
377
367
|
end
|
|
378
368
|
|
|
379
369
|
log_instance
|
|
@@ -386,6 +376,7 @@ module CMDx
|
|
|
386
376
|
# @option return [Array<Symbol>] :tags The task tags
|
|
387
377
|
# @option return [String] :class The task class name
|
|
388
378
|
# @option return [String] :id The task identifier
|
|
379
|
+
# @option return [Hash] :context The task context (when dump_context is true)
|
|
389
380
|
#
|
|
390
381
|
# @return [Hash] A hash representation of the task
|
|
391
382
|
#
|
|
@@ -399,12 +390,18 @@ module CMDx
|
|
|
399
390
|
{
|
|
400
391
|
index: result.index,
|
|
401
392
|
chain_id: chain.id,
|
|
402
|
-
type: self.class.
|
|
403
|
-
tags: self.class.settings[:tags],
|
|
393
|
+
type: self.class.type,
|
|
404
394
|
class: self.class.name,
|
|
395
|
+
id:,
|
|
405
396
|
dry_run: dry_run?,
|
|
406
|
-
|
|
407
|
-
}
|
|
397
|
+
tags: self.class.settings.tags
|
|
398
|
+
}.tap do |hash|
|
|
399
|
+
if self.class.settings.dump_context
|
|
400
|
+
# Large context can make dumps and logs very noisy,
|
|
401
|
+
# so only include it if explicitly enabled
|
|
402
|
+
hash[:context] = context.to_h
|
|
403
|
+
end
|
|
404
|
+
end
|
|
408
405
|
end
|
|
409
406
|
|
|
410
407
|
# @return [String] A string representation of the task
|
data/lib/cmdx/utils/format.rb
CHANGED
|
@@ -32,7 +32,7 @@ module CMDx
|
|
|
32
32
|
#
|
|
33
33
|
# @rbs (untyped message) -> untyped
|
|
34
34
|
def to_log(message)
|
|
35
|
-
if message.respond_to?(:to_h) && message.class
|
|
35
|
+
if message.respond_to?(:to_h) && cmdx_based_object?(message.class)
|
|
36
36
|
message.to_h
|
|
37
37
|
else
|
|
38
38
|
message
|
|
@@ -61,6 +61,22 @@ module CMDx
|
|
|
61
61
|
hash.map(&block).join(" ")
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# Checks if a class belongs to the CMDx namespace, caching per class.
|
|
67
|
+
#
|
|
68
|
+
# @param klass [Class] The class to check
|
|
69
|
+
#
|
|
70
|
+
# @return [Boolean] true if the class is in the CMDx namespace
|
|
71
|
+
#
|
|
72
|
+
# @rbs (Class klass) -> bool
|
|
73
|
+
def cmdx_based_object?(klass)
|
|
74
|
+
@cmdx_classes ||= {}
|
|
75
|
+
return @cmdx_classes[klass] if @cmdx_classes.key?(klass)
|
|
76
|
+
|
|
77
|
+
@cmdx_classes[klass] = klass.ancestors.any? { |a| a.name&.start_with?("CMDx::") }
|
|
78
|
+
end
|
|
79
|
+
|
|
64
80
|
end
|
|
65
81
|
end
|
|
66
82
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CMDx
|
|
4
|
+
module Utils
|
|
5
|
+
# Provides normalization utilities for a variety of objects
|
|
6
|
+
# into consistent formats.
|
|
7
|
+
module Normalize
|
|
8
|
+
|
|
9
|
+
extend self
|
|
10
|
+
|
|
11
|
+
# Normalizes an exception into a string representation.
|
|
12
|
+
#
|
|
13
|
+
# @param exception [Exception] The exception to normalize
|
|
14
|
+
#
|
|
15
|
+
# @return [String] The normalized exception string
|
|
16
|
+
#
|
|
17
|
+
# @example From exception
|
|
18
|
+
# Normalize.exception(StandardError.new("test"))
|
|
19
|
+
# # => "[StandardError] test"
|
|
20
|
+
#
|
|
21
|
+
# @rbs (Exception exception) -> String
|
|
22
|
+
def exception(exception)
|
|
23
|
+
"[#{exception.class}] #{exception.message}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Normalizes an object into an array of unique status strings.
|
|
27
|
+
#
|
|
28
|
+
# @param object [Object] The object to normalize into status strings
|
|
29
|
+
#
|
|
30
|
+
# @return [Array<String>] Unique status strings
|
|
31
|
+
#
|
|
32
|
+
# @example From array of symbols
|
|
33
|
+
# Normalize.statuses([:success, :pending, :success])
|
|
34
|
+
# # => ["success", "pending"]
|
|
35
|
+
# @example From single value
|
|
36
|
+
# Normalize.statuses(:success)
|
|
37
|
+
# # => ["success"]
|
|
38
|
+
# @example From nil
|
|
39
|
+
# Normalize.statuses(nil)
|
|
40
|
+
# # => []
|
|
41
|
+
#
|
|
42
|
+
# @rbs (untyped object) -> Array[String]
|
|
43
|
+
def statuses(object)
|
|
44
|
+
ary = Wrap.array(object)
|
|
45
|
+
return EMPTY_ARRAY if ary.empty?
|
|
46
|
+
|
|
47
|
+
ary.map(&:to_s).uniq
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CMDx
|
|
4
|
+
module Utils
|
|
5
|
+
# Provides array wrapping utilities for normalizing input values
|
|
6
|
+
# into consistent array structures.
|
|
7
|
+
module Wrap
|
|
8
|
+
|
|
9
|
+
extend self
|
|
10
|
+
|
|
11
|
+
# Wraps an object in an array if it is not already an array.
|
|
12
|
+
#
|
|
13
|
+
# @param object [Object] The object to wrap in an array
|
|
14
|
+
#
|
|
15
|
+
# @return [Array] The wrapped array
|
|
16
|
+
#
|
|
17
|
+
# @example Already an array
|
|
18
|
+
# Wrap.array([1, 2, 3])
|
|
19
|
+
# # => [1, 2, 3]
|
|
20
|
+
# @example Single value
|
|
21
|
+
# Wrap.array(1)
|
|
22
|
+
# # => [1]
|
|
23
|
+
# @example Nil value
|
|
24
|
+
# Wrap.array(nil)
|
|
25
|
+
# # => []
|
|
26
|
+
#
|
|
27
|
+
# @rbs (untyped object) -> Array[untyped]
|
|
28
|
+
def array(object)
|
|
29
|
+
case object
|
|
30
|
+
when Array then object
|
|
31
|
+
when NilClass then EMPTY_ARRAY
|
|
32
|
+
else Array(object)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -3,21 +3,13 @@
|
|
|
3
3
|
module CMDx
|
|
4
4
|
# Registry for managing validation rules and their corresponding validator classes.
|
|
5
5
|
# Provides methods to register, deregister, and execute validators against task values.
|
|
6
|
+
#
|
|
7
|
+
# Supports copy-on-write semantics: a duped registry shares the parent's
|
|
8
|
+
# data until a write operation triggers materialization.
|
|
6
9
|
class ValidatorRegistry
|
|
7
10
|
|
|
8
11
|
extend Forwardable
|
|
9
12
|
|
|
10
|
-
# Returns the internal registry mapping validator types to classes.
|
|
11
|
-
#
|
|
12
|
-
# @return [Hash{Symbol => Class}] Hash of validator type names to validator classes
|
|
13
|
-
#
|
|
14
|
-
# @example
|
|
15
|
-
# registry.registry # => { presence: Validators::Presence, format: Validators::Format }
|
|
16
|
-
#
|
|
17
|
-
# @rbs @registry: Hash[Symbol, Class]
|
|
18
|
-
attr_reader :registry
|
|
19
|
-
alias to_h registry
|
|
20
|
-
|
|
21
13
|
def_delegators :registry, :keys
|
|
22
14
|
|
|
23
15
|
# Initialize a new validator registry with default validators.
|
|
@@ -39,14 +31,30 @@ module CMDx
|
|
|
39
31
|
}
|
|
40
32
|
end
|
|
41
33
|
|
|
42
|
-
#
|
|
34
|
+
# Sets up copy-on-write state when duplicated via dup.
|
|
35
|
+
#
|
|
36
|
+
# @param source [ValidatorRegistry] The registry being duplicated
|
|
37
|
+
#
|
|
38
|
+
# @rbs (ValidatorRegistry source) -> void
|
|
39
|
+
def initialize_dup(source)
|
|
40
|
+
@parent = source
|
|
41
|
+
@registry = nil
|
|
42
|
+
super
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns the internal registry mapping validator types to classes.
|
|
46
|
+
# Delegates to the parent registry when not yet materialized.
|
|
43
47
|
#
|
|
44
|
-
# @return [
|
|
48
|
+
# @return [Hash{Symbol => Class}] Hash of validator type names to validator classes
|
|
45
49
|
#
|
|
46
|
-
# @
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
# @example
|
|
51
|
+
# registry.registry # => { presence: Validators::Presence, format: Validators::Format }
|
|
52
|
+
#
|
|
53
|
+
# @rbs () -> Hash[Symbol, Class]
|
|
54
|
+
def registry
|
|
55
|
+
@registry || @parent.registry
|
|
49
56
|
end
|
|
57
|
+
alias to_h registry
|
|
50
58
|
|
|
51
59
|
# Register a new validator class with the given name.
|
|
52
60
|
#
|
|
@@ -61,7 +69,9 @@ module CMDx
|
|
|
61
69
|
#
|
|
62
70
|
# @rbs ((String | Symbol) name, Class validator) -> self
|
|
63
71
|
def register(name, validator)
|
|
64
|
-
|
|
72
|
+
materialize!
|
|
73
|
+
|
|
74
|
+
@registry[name.to_sym] = validator
|
|
65
75
|
self
|
|
66
76
|
end
|
|
67
77
|
|
|
@@ -77,7 +87,9 @@ module CMDx
|
|
|
77
87
|
#
|
|
78
88
|
# @rbs ((String | Symbol) name) -> self
|
|
79
89
|
def deregister(name)
|
|
80
|
-
|
|
90
|
+
materialize!
|
|
91
|
+
|
|
92
|
+
@registry.delete(name.to_sym)
|
|
81
93
|
self
|
|
82
94
|
end
|
|
83
95
|
|
|
@@ -96,7 +108,7 @@ module CMDx
|
|
|
96
108
|
# registry.validate(:length, task, password, { min: 8, allow_nil: false })
|
|
97
109
|
#
|
|
98
110
|
# @rbs (Symbol type, Task task, untyped value, untyped options) -> untyped
|
|
99
|
-
def validate(type, task, value, options =
|
|
111
|
+
def validate(type, task, value, options = EMPTY_HASH)
|
|
100
112
|
raise TypeError, "unknown validator type #{type.inspect}" unless registry.key?(type)
|
|
101
113
|
|
|
102
114
|
match =
|
|
@@ -114,5 +126,18 @@ module CMDx
|
|
|
114
126
|
Utils::Call.invoke(task, registry[type], value, options)
|
|
115
127
|
end
|
|
116
128
|
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
# Copies the parent's registry data into this instance,
|
|
132
|
+
# severing the copy-on-write link.
|
|
133
|
+
#
|
|
134
|
+
# @rbs () -> void
|
|
135
|
+
def materialize!
|
|
136
|
+
return if @registry
|
|
137
|
+
|
|
138
|
+
@registry = @parent.registry.dup
|
|
139
|
+
@parent = nil
|
|
140
|
+
end
|
|
141
|
+
|
|
117
142
|
end
|
|
118
143
|
end
|