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,138 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module CMDx
|
|
4
|
-
# Registry for managing type coercion handlers.
|
|
5
|
-
#
|
|
6
|
-
# Provides a centralized way to register, deregister, and execute type coercions
|
|
7
|
-
# for various data types including arrays, numbers, dates, and other primitives.
|
|
8
|
-
#
|
|
9
|
-
# Supports copy-on-write semantics: a duped registry shares the parent's
|
|
10
|
-
# data until a write operation triggers materialization.
|
|
11
|
-
class CoercionRegistry
|
|
12
|
-
|
|
13
|
-
# Initialize a new coercion registry.
|
|
14
|
-
#
|
|
15
|
-
# @param registry [Hash{Symbol => Class}, nil] optional initial registry hash
|
|
16
|
-
#
|
|
17
|
-
# @example
|
|
18
|
-
# registry = CoercionRegistry.new
|
|
19
|
-
# registry = CoercionRegistry.new(custom: CustomCoercion)
|
|
20
|
-
#
|
|
21
|
-
# @rbs (?Hash[Symbol, Class]? registry) -> void
|
|
22
|
-
def initialize(registry = nil)
|
|
23
|
-
@registry = registry || {
|
|
24
|
-
array: Coercions::Array,
|
|
25
|
-
big_decimal: Coercions::BigDecimal,
|
|
26
|
-
boolean: Coercions::Boolean,
|
|
27
|
-
complex: Coercions::Complex,
|
|
28
|
-
date: Coercions::Date,
|
|
29
|
-
datetime: Coercions::DateTime,
|
|
30
|
-
float: Coercions::Float,
|
|
31
|
-
hash: Coercions::Hash,
|
|
32
|
-
integer: Coercions::Integer,
|
|
33
|
-
rational: Coercions::Rational,
|
|
34
|
-
string: Coercions::String,
|
|
35
|
-
time: Coercions::Time
|
|
36
|
-
}
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Sets up copy-on-write state when duplicated via dup.
|
|
40
|
-
#
|
|
41
|
-
# @param source [CoercionRegistry] The registry being duplicated
|
|
42
|
-
#
|
|
43
|
-
# @rbs (CoercionRegistry source) -> void
|
|
44
|
-
def initialize_dup(source)
|
|
45
|
-
@parent = source
|
|
46
|
-
@registry = nil
|
|
47
|
-
super
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Returns the internal registry mapping coercion types to handler classes.
|
|
51
|
-
# Delegates to the parent registry when not yet materialized.
|
|
52
|
-
#
|
|
53
|
-
# @return [Hash{Symbol => Class}] Hash of coercion type names to coercion classes
|
|
54
|
-
#
|
|
55
|
-
# @example
|
|
56
|
-
# registry.registry # => { integer: Coercions::Integer, boolean: Coercions::Boolean }
|
|
57
|
-
#
|
|
58
|
-
# @rbs () -> Hash[Symbol, Class]
|
|
59
|
-
def registry
|
|
60
|
-
@registry || @parent.registry
|
|
61
|
-
end
|
|
62
|
-
alias to_h registry
|
|
63
|
-
|
|
64
|
-
# Register a new coercion handler for a type.
|
|
65
|
-
#
|
|
66
|
-
# @param name [Symbol, String] the type name to register
|
|
67
|
-
# @param coercion [Class] the coercion class to handle this type
|
|
68
|
-
#
|
|
69
|
-
# @return [CoercionRegistry] self for method chaining
|
|
70
|
-
#
|
|
71
|
-
# @example
|
|
72
|
-
# registry.register(:custom_type, CustomCoercion)
|
|
73
|
-
# registry.register("another_type", AnotherCoercion)
|
|
74
|
-
#
|
|
75
|
-
# @rbs ((Symbol | String) name, Class coercion) -> self
|
|
76
|
-
def register(name, coercion)
|
|
77
|
-
materialize!
|
|
78
|
-
|
|
79
|
-
@registry[name.to_sym] = coercion
|
|
80
|
-
self
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Remove a coercion handler for a type.
|
|
84
|
-
#
|
|
85
|
-
# @param name [Symbol, String] the type name to deregister
|
|
86
|
-
#
|
|
87
|
-
# @return [CoercionRegistry] self for method chaining
|
|
88
|
-
#
|
|
89
|
-
# @example
|
|
90
|
-
# registry.deregister(:custom_type)
|
|
91
|
-
# registry.deregister("another_type")
|
|
92
|
-
#
|
|
93
|
-
# @rbs ((Symbol | String) name) -> self
|
|
94
|
-
def deregister(name)
|
|
95
|
-
materialize!
|
|
96
|
-
|
|
97
|
-
@registry.delete(name.to_sym)
|
|
98
|
-
self
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Coerce a value to the specified type using the registered handler.
|
|
102
|
-
#
|
|
103
|
-
# @param type [Symbol] the type to coerce to
|
|
104
|
-
# @param task [Object] the task context for the coercion
|
|
105
|
-
# @param value [Object] the value to coerce
|
|
106
|
-
# @param options [Hash] additional options for the coercion
|
|
107
|
-
# @option options [Object] :* Any coercion option key-value pairs
|
|
108
|
-
#
|
|
109
|
-
# @return [Object] the coerced value
|
|
110
|
-
#
|
|
111
|
-
# @raise [TypeError] when the type is not registered
|
|
112
|
-
#
|
|
113
|
-
# @example
|
|
114
|
-
# result = registry.coerce(:integer, task, "42")
|
|
115
|
-
# result = registry.coerce(:boolean, task, "true", strict: true)
|
|
116
|
-
#
|
|
117
|
-
# @rbs (Symbol type, untyped task, untyped value, ?Hash[Symbol, untyped] options) -> untyped
|
|
118
|
-
def coerce(type, task, value, options = EMPTY_HASH)
|
|
119
|
-
raise TypeError, "unknown coercion type #{type.inspect}" unless registry.key?(type)
|
|
120
|
-
|
|
121
|
-
Utils::Call.invoke(task, registry[type], value, options)
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
private
|
|
125
|
-
|
|
126
|
-
# Copies the parent's registry data into this instance,
|
|
127
|
-
# severing the copy-on-write link.
|
|
128
|
-
#
|
|
129
|
-
# @rbs () -> void
|
|
130
|
-
def materialize!
|
|
131
|
-
return if @registry
|
|
132
|
-
|
|
133
|
-
@registry = @parent.registry.dup
|
|
134
|
-
@parent = nil
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
end
|
|
138
|
-
end
|
data/lib/cmdx/deprecator.rb
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module CMDx
|
|
4
|
-
# Handles deprecation warnings and restrictions for tasks.
|
|
5
|
-
#
|
|
6
|
-
# The Deprecator module provides functionality to restrict usage of deprecated
|
|
7
|
-
# tasks based on configuration settings. It supports different deprecation
|
|
8
|
-
# behaviors including warnings, logging, and errors.
|
|
9
|
-
module Deprecator
|
|
10
|
-
|
|
11
|
-
extend self
|
|
12
|
-
|
|
13
|
-
# @rbs RAISE_REGEXP: Regexp
|
|
14
|
-
RAISE_REGEXP = /\Araise\z/
|
|
15
|
-
private_constant :RAISE_REGEXP
|
|
16
|
-
|
|
17
|
-
# @rbs LOG_REGEXP: Regexp
|
|
18
|
-
LOG_REGEXP = /\Alog\z/
|
|
19
|
-
private_constant :LOG_REGEXP
|
|
20
|
-
|
|
21
|
-
# @rbs WARN_REGEXP: Regexp
|
|
22
|
-
WARN_REGEXP = /\Awarn\z/
|
|
23
|
-
private_constant :WARN_REGEXP
|
|
24
|
-
|
|
25
|
-
# @rbs EVAL: Proc
|
|
26
|
-
EVAL = proc do |target, callable|
|
|
27
|
-
case callable
|
|
28
|
-
when NilClass, FalseClass, TrueClass then !!callable
|
|
29
|
-
when RAISE_REGEXP, LOG_REGEXP, WARN_REGEXP then callable
|
|
30
|
-
when Symbol then target.send(callable)
|
|
31
|
-
when Proc then target.instance_eval(&callable)
|
|
32
|
-
else
|
|
33
|
-
raise "cannot evaluate #{callable.inspect}" unless callable.respond_to?(:call)
|
|
34
|
-
|
|
35
|
-
callable.call(target)
|
|
36
|
-
end
|
|
37
|
-
end.freeze
|
|
38
|
-
private_constant :EVAL
|
|
39
|
-
|
|
40
|
-
# Restricts task usage based on deprecation settings.
|
|
41
|
-
#
|
|
42
|
-
# @param task [Object] The task object to check for deprecation
|
|
43
|
-
# @option task.class.settings.deprecate [Symbol, Proc, String, Boolean]
|
|
44
|
-
# The deprecation configuration for the task
|
|
45
|
-
# @option task.class.settings.deprecate :raise Raises DeprecationError
|
|
46
|
-
# @option task.class.settings.deprecate :log Logs deprecation warning
|
|
47
|
-
# @option task.class.settings.deprecate :warn Outputs warning to stderr
|
|
48
|
-
# @option task.class.settings.deprecate true Raises DeprecationError
|
|
49
|
-
# @option task.class.settings.deprecate false No action taken
|
|
50
|
-
# @option task.class.settings.deprecate nil No action taken
|
|
51
|
-
#
|
|
52
|
-
# @raise [DeprecationError] When deprecation type is :raise or true
|
|
53
|
-
# @raise [RuntimeError] When deprecation type is unknown
|
|
54
|
-
#
|
|
55
|
-
# @example
|
|
56
|
-
# class MyTask
|
|
57
|
-
# settings(deprecate: :warn)
|
|
58
|
-
# end
|
|
59
|
-
#
|
|
60
|
-
# MyTask.new # => [MyTask] DEPRECATED: migrate to a replacement or discontinue use
|
|
61
|
-
#
|
|
62
|
-
# @rbs (Task task) -> void
|
|
63
|
-
def restrict(task)
|
|
64
|
-
setting = task.class.settings.deprecate
|
|
65
|
-
return unless setting
|
|
66
|
-
|
|
67
|
-
case type = EVAL.call(task, setting)
|
|
68
|
-
when NilClass, FalseClass then nil # Do nothing
|
|
69
|
-
when TrueClass, RAISE_REGEXP then raise DeprecationError, "#{task.class.name} usage prohibited"
|
|
70
|
-
when LOG_REGEXP then task.logger.warn { "DEPRECATED: migrate to a replacement or discontinue use" }
|
|
71
|
-
when WARN_REGEXP then warn("[#{task.class.name}] DEPRECATED: migrate to a replacement or discontinue use", category: :deprecated)
|
|
72
|
-
else raise "unknown deprecation type #{type.inspect}"
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
end
|
|
77
|
-
end
|
data/lib/cmdx/exception.rb
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module CMDx
|
|
4
|
-
|
|
5
|
-
# Base exception class for all CMDx-related errors.
|
|
6
|
-
#
|
|
7
|
-
# This serves as the root exception class for all errors raised by the CMDx
|
|
8
|
-
# framework. It inherits from StandardError and provides a common base for
|
|
9
|
-
# handling CMDx-specific exceptions.
|
|
10
|
-
Exception = Error = Class.new(StandardError)
|
|
11
|
-
|
|
12
|
-
# Raised when attribute coercion fails during task execution.
|
|
13
|
-
#
|
|
14
|
-
# This error occurs when a attribute value cannot be converted to the expected
|
|
15
|
-
# type using the registered coercion handlers. It indicates that the provided
|
|
16
|
-
# value is incompatible with the attribute's defined type.
|
|
17
|
-
CoercionError = Class.new(Error)
|
|
18
|
-
|
|
19
|
-
# Raised when a deprecated task is used.
|
|
20
|
-
#
|
|
21
|
-
# This error occurs when a deprecated task is called. It indicates that the
|
|
22
|
-
# task is no longer supported and should be replaced with a newer alternative.
|
|
23
|
-
DeprecationError = Class.new(Error)
|
|
24
|
-
|
|
25
|
-
# Raised when an abstract method is called without being implemented.
|
|
26
|
-
#
|
|
27
|
-
# This error occurs when a subclass fails to implement required abstract
|
|
28
|
-
# methods such as 'task' in tasks. It indicates incomplete implementation
|
|
29
|
-
# of required functionality.
|
|
30
|
-
UndefinedMethodError = Class.new(Error)
|
|
31
|
-
|
|
32
|
-
# Error raised when task execution exceeds the configured timeout limit.
|
|
33
|
-
#
|
|
34
|
-
# This error occurs when a task takes longer to execute than the specified
|
|
35
|
-
# time limit. Timeout errors are raised by Ruby's Timeout module and are
|
|
36
|
-
# caught by the middleware to properly fail the task with timeout information.
|
|
37
|
-
TimeoutError = Class.new(Interrupt)
|
|
38
|
-
|
|
39
|
-
# Raised when attribute validation fails during task execution.
|
|
40
|
-
#
|
|
41
|
-
# This error occurs when a attribute value doesn't meet the validation criteria
|
|
42
|
-
# defined by the validator. It indicates that the provided value violates
|
|
43
|
-
# business rules or data integrity constraints.
|
|
44
|
-
ValidationError = Class.new(Error)
|
|
45
|
-
|
|
46
|
-
end
|
data/lib/cmdx/executor.rb
DELETED
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module CMDx
|
|
4
|
-
# Executes CMDx tasks with middleware support, error handling, and lifecycle management.
|
|
5
|
-
#
|
|
6
|
-
# The Executor class is responsible for orchestrating task execution, including
|
|
7
|
-
# pre-execution validation, execution with middleware, post-execution callbacks,
|
|
8
|
-
# and proper error handling for different types of failures.
|
|
9
|
-
class Executor
|
|
10
|
-
|
|
11
|
-
extend Forwardable
|
|
12
|
-
|
|
13
|
-
# @rbs STATE_CALLBACKS: Hash[String, Symbol]
|
|
14
|
-
STATE_CALLBACKS = Result::STATES.to_h { |s| [s, :"on_#{s}"] }.freeze
|
|
15
|
-
private_constant :STATE_CALLBACKS
|
|
16
|
-
|
|
17
|
-
# @rbs STATUS_CALLBACKS: Hash[String, Symbol]
|
|
18
|
-
STATUS_CALLBACKS = Result::STATUSES.to_h { |s| [s, :"on_#{s}"] }.freeze
|
|
19
|
-
private_constant :STATUS_CALLBACKS
|
|
20
|
-
|
|
21
|
-
# Returns the task being executed.
|
|
22
|
-
#
|
|
23
|
-
# @return [Task] The task instance
|
|
24
|
-
#
|
|
25
|
-
# @example
|
|
26
|
-
# executor.task.id # => "abc123"
|
|
27
|
-
#
|
|
28
|
-
# @rbs @task: Task
|
|
29
|
-
attr_reader :task
|
|
30
|
-
|
|
31
|
-
def_delegators :task, :result
|
|
32
|
-
|
|
33
|
-
# @param task [CMDx::Task] The task to execute
|
|
34
|
-
#
|
|
35
|
-
# @return [CMDx::Executor] A new executor instance
|
|
36
|
-
#
|
|
37
|
-
# @example
|
|
38
|
-
# executor = CMDx::Executor.new(my_task)
|
|
39
|
-
#
|
|
40
|
-
# @rbs (Task task) -> void
|
|
41
|
-
def initialize(task)
|
|
42
|
-
@task = task
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Executes a task with optional exception raising.
|
|
46
|
-
#
|
|
47
|
-
# @param task [CMDx::Task] The task to execute
|
|
48
|
-
# @param raise [Boolean] Whether to raise exceptions (default: false)
|
|
49
|
-
#
|
|
50
|
-
# @return [CMDx::Result] The execution result
|
|
51
|
-
#
|
|
52
|
-
# @raise [StandardError] When raise is true and execution fails
|
|
53
|
-
#
|
|
54
|
-
# @example
|
|
55
|
-
# CMDx::Executor.execute(my_task)
|
|
56
|
-
# CMDx::Executor.execute(my_task, raise: true)
|
|
57
|
-
#
|
|
58
|
-
# @rbs (Task task, raise: bool) -> Result
|
|
59
|
-
def self.execute(task, raise: false)
|
|
60
|
-
instance = new(task)
|
|
61
|
-
raise ? instance.execute! : instance.execute
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Executes the task with graceful error handling.
|
|
65
|
-
#
|
|
66
|
-
# @return [CMDx::Result] The execution result
|
|
67
|
-
#
|
|
68
|
-
# @example
|
|
69
|
-
# executor = CMDx::Executor.new(my_task)
|
|
70
|
-
# result = executor.execute
|
|
71
|
-
#
|
|
72
|
-
# @rbs () -> Result
|
|
73
|
-
def execute
|
|
74
|
-
task.class.settings.middlewares.call!(task) do
|
|
75
|
-
pre_execution! unless @pre_execution
|
|
76
|
-
execution!
|
|
77
|
-
verify_context_returns!
|
|
78
|
-
rescue UndefinedMethodError => e
|
|
79
|
-
raise_exception(e)
|
|
80
|
-
rescue Fault => e
|
|
81
|
-
result.throw!(e.result, halt: false, cause: e)
|
|
82
|
-
rescue StandardError => e
|
|
83
|
-
retry if retry_execution?(e)
|
|
84
|
-
result.fail!(Utils::Normalize.exception(e), halt: false, cause: e, source: :exception)
|
|
85
|
-
task.class.settings.exception_handler&.call(task, e)
|
|
86
|
-
ensure
|
|
87
|
-
result.executed!
|
|
88
|
-
post_execution!
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
verify_middleware_yield!
|
|
92
|
-
finalize_execution!
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Executes the task with exception raising on failure.
|
|
96
|
-
#
|
|
97
|
-
# @return [CMDx::Result] The execution result
|
|
98
|
-
#
|
|
99
|
-
# @raise [StandardError] When execution fails
|
|
100
|
-
#
|
|
101
|
-
# @example
|
|
102
|
-
# executor = CMDx::Executor.new(my_task)
|
|
103
|
-
# result = executor.execute!
|
|
104
|
-
#
|
|
105
|
-
# @rbs () -> Result
|
|
106
|
-
def execute!
|
|
107
|
-
task.class.settings.middlewares.call!(task) do
|
|
108
|
-
pre_execution! unless @pre_execution
|
|
109
|
-
execution!
|
|
110
|
-
verify_context_returns!
|
|
111
|
-
rescue UndefinedMethodError => e
|
|
112
|
-
raise_exception(e)
|
|
113
|
-
rescue Fault => e
|
|
114
|
-
result.throw!(e.result, halt: false, cause: e)
|
|
115
|
-
|
|
116
|
-
if halt_execution?(e)
|
|
117
|
-
raise_exception(e)
|
|
118
|
-
else
|
|
119
|
-
result.executed!
|
|
120
|
-
post_execution!
|
|
121
|
-
end
|
|
122
|
-
rescue StandardError => e
|
|
123
|
-
retry if retry_execution?(e)
|
|
124
|
-
result.fail!(Utils::Normalize.exception(e), halt: false, cause: e, source: :exception)
|
|
125
|
-
raise_exception(e)
|
|
126
|
-
else
|
|
127
|
-
result.executed!
|
|
128
|
-
post_execution!
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
verify_middleware_yield!
|
|
132
|
-
finalize_execution!
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
protected
|
|
136
|
-
|
|
137
|
-
# Determines if execution should halt based on breakpoint configuration.
|
|
138
|
-
# Returns false when the result was created with +strict: false+.
|
|
139
|
-
#
|
|
140
|
-
# @param exception [Exception] The exception that occurred
|
|
141
|
-
#
|
|
142
|
-
# @return [Boolean] Whether execution should halt
|
|
143
|
-
#
|
|
144
|
-
# @rbs (Exception exception) -> bool
|
|
145
|
-
def halt_execution?(exception)
|
|
146
|
-
return false unless exception.result.strict?
|
|
147
|
-
|
|
148
|
-
@halt_statuses ||= Utils::Normalize.statuses(
|
|
149
|
-
task.class.settings.breakpoints ||
|
|
150
|
-
task.class.settings.task_breakpoints
|
|
151
|
-
).freeze
|
|
152
|
-
|
|
153
|
-
@halt_statuses.include?(exception.result.status)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
# Determines if execution should be retried based on retry configuration.
|
|
157
|
-
#
|
|
158
|
-
# @param exception [Exception] The exception that occurred
|
|
159
|
-
#
|
|
160
|
-
# @return [Boolean] Whether execution should be retried
|
|
161
|
-
#
|
|
162
|
-
# @rbs (Exception exception) -> bool
|
|
163
|
-
def retry_execution?(exception)
|
|
164
|
-
@retry ||= Retry.new(task)
|
|
165
|
-
|
|
166
|
-
return false unless @retry.available? && @retry.remaining?
|
|
167
|
-
return false unless @retry.exception?(exception)
|
|
168
|
-
|
|
169
|
-
result.retries += 1
|
|
170
|
-
|
|
171
|
-
task.logger.warn do
|
|
172
|
-
reason = Utils::Normalize.exception(exception)
|
|
173
|
-
task.to_h.merge!(reason:, remaining_retries: @retry.remaining)
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
task.errors.clear
|
|
177
|
-
|
|
178
|
-
wait = @retry.wait
|
|
179
|
-
sleep(wait) if wait.positive?
|
|
180
|
-
|
|
181
|
-
true
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
# Raises an exception and clears the chain.
|
|
185
|
-
#
|
|
186
|
-
# @param exception [Exception] The exception to raise
|
|
187
|
-
#
|
|
188
|
-
# @raise [Exception] The provided exception
|
|
189
|
-
#
|
|
190
|
-
# @rbs (Exception exception) -> void
|
|
191
|
-
def raise_exception(exception)
|
|
192
|
-
Chain.clear
|
|
193
|
-
|
|
194
|
-
raise(exception)
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
# Invokes callbacks of a specific type for the task.
|
|
198
|
-
#
|
|
199
|
-
# @param type [Symbol] The type of callback to invoke
|
|
200
|
-
#
|
|
201
|
-
# @return [void]
|
|
202
|
-
#
|
|
203
|
-
# @example
|
|
204
|
-
# invoke_callbacks(:before_execution)
|
|
205
|
-
#
|
|
206
|
-
# @rbs (Symbol type) -> void
|
|
207
|
-
def invoke_callbacks(type)
|
|
208
|
-
task.class.settings.callbacks.invoke(type, task)
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
private
|
|
212
|
-
|
|
213
|
-
# Performs pre-execution tasks including validation and attribute verification.
|
|
214
|
-
#
|
|
215
|
-
# @rbs () -> void
|
|
216
|
-
def pre_execution!
|
|
217
|
-
@pre_execution = true
|
|
218
|
-
|
|
219
|
-
invoke_callbacks(:before_validation)
|
|
220
|
-
|
|
221
|
-
task.class.settings.attributes.define_and_verify(task)
|
|
222
|
-
return if task.errors.empty?
|
|
223
|
-
|
|
224
|
-
result.fail!(
|
|
225
|
-
Locale.t("cmdx.faults.invalid"),
|
|
226
|
-
source: :validation,
|
|
227
|
-
errors: {
|
|
228
|
-
full_message: task.errors.to_s,
|
|
229
|
-
messages: task.errors.to_h
|
|
230
|
-
}
|
|
231
|
-
)
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
# Executes the main task logic.
|
|
235
|
-
# Wraps task.work in catch(:cmdx_halt) so that success! can halt early.
|
|
236
|
-
#
|
|
237
|
-
# @rbs () -> void
|
|
238
|
-
def execution!
|
|
239
|
-
invoke_callbacks(:before_execution)
|
|
240
|
-
|
|
241
|
-
result.executing!
|
|
242
|
-
catch(:cmdx_halt) { task.work }
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
# Verifies that all declared returns are present in the context after execution.
|
|
246
|
-
#
|
|
247
|
-
# @rbs () -> void
|
|
248
|
-
def verify_context_returns!
|
|
249
|
-
return unless result.success?
|
|
250
|
-
|
|
251
|
-
returns = Utils::Wrap.array(task.class.settings.returns)
|
|
252
|
-
missing = returns.reject { |name| task.context.key?(name) }
|
|
253
|
-
return if missing.empty?
|
|
254
|
-
|
|
255
|
-
missing.each { |name| task.errors.add(name, Locale.t("cmdx.returns.missing")) }
|
|
256
|
-
|
|
257
|
-
result.fail!(
|
|
258
|
-
Locale.t("cmdx.faults.invalid"),
|
|
259
|
-
source: :context,
|
|
260
|
-
errors: {
|
|
261
|
-
full_message: task.errors.to_s,
|
|
262
|
-
messages: task.errors.to_h
|
|
263
|
-
}
|
|
264
|
-
)
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
# Performs post-execution tasks including callback invocation.
|
|
268
|
-
#
|
|
269
|
-
# @rbs () -> void
|
|
270
|
-
def post_execution!
|
|
271
|
-
return if task.class.settings.callbacks.empty?
|
|
272
|
-
|
|
273
|
-
invoke_callbacks(STATE_CALLBACKS[result.state])
|
|
274
|
-
invoke_callbacks(:on_executed) if result.executed?
|
|
275
|
-
|
|
276
|
-
invoke_callbacks(STATUS_CALLBACKS[result.status])
|
|
277
|
-
invoke_callbacks(:on_good) if result.good?
|
|
278
|
-
invoke_callbacks(:on_bad) if result.bad?
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
# Detects if middleware swallowed the block without yielding.
|
|
282
|
-
# When this happens the result is still in the initialized state.
|
|
283
|
-
#
|
|
284
|
-
# @rbs () -> void
|
|
285
|
-
def verify_middleware_yield!
|
|
286
|
-
return unless result.initialized?
|
|
287
|
-
|
|
288
|
-
result.fail!(
|
|
289
|
-
Locale.t("cmdx.faults.invalid"),
|
|
290
|
-
halt: false,
|
|
291
|
-
source: :middleware
|
|
292
|
-
)
|
|
293
|
-
result.executed!
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
# Finalizes execution by freezing the task, logging results, and rolling back work.
|
|
297
|
-
#
|
|
298
|
-
# @rbs () -> void
|
|
299
|
-
def finalize_execution!
|
|
300
|
-
log_execution!
|
|
301
|
-
log_backtrace! if task.class.settings.backtrace
|
|
302
|
-
|
|
303
|
-
rollback_execution!
|
|
304
|
-
freeze_execution!
|
|
305
|
-
clear_chain!
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
# Logs the execution result at the configured log level.
|
|
309
|
-
#
|
|
310
|
-
# @rbs () -> void
|
|
311
|
-
def log_execution!
|
|
312
|
-
task.logger.info { result.to_h }
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
# Logs the backtrace of the exception if the task failed.
|
|
316
|
-
#
|
|
317
|
-
# @rbs () -> void
|
|
318
|
-
def log_backtrace!
|
|
319
|
-
return unless result.failed?
|
|
320
|
-
|
|
321
|
-
exception = result.caused_failure&.cause
|
|
322
|
-
return if exception.nil? || exception.is_a?(Fault)
|
|
323
|
-
|
|
324
|
-
task.logger.error do
|
|
325
|
-
Utils::Normalize.exception(exception) << "\n" <<
|
|
326
|
-
if (cleaner = task.class.settings.backtrace_cleaner)
|
|
327
|
-
cleaner.call(exception.backtrace).join("\n\t")
|
|
328
|
-
else
|
|
329
|
-
exception.full_message(highlight: false)
|
|
330
|
-
end
|
|
331
|
-
end
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
# Freezes the task and its associated objects to prevent modifications.
|
|
335
|
-
#
|
|
336
|
-
# @rbs () -> void
|
|
337
|
-
def freeze_execution!
|
|
338
|
-
# Stubbing on frozen objects is not allowed in most test environments.
|
|
339
|
-
return unless CMDx.configuration.freeze_results
|
|
340
|
-
|
|
341
|
-
task.freeze
|
|
342
|
-
result.freeze
|
|
343
|
-
|
|
344
|
-
# Freezing the context and chain can only be done once the outer-most
|
|
345
|
-
# task has completed.
|
|
346
|
-
return unless result.index.zero?
|
|
347
|
-
|
|
348
|
-
task.context.freeze
|
|
349
|
-
task.chain.freeze
|
|
350
|
-
end
|
|
351
|
-
|
|
352
|
-
# Clears the chain if the task is the outermost (top-level) task
|
|
353
|
-
# and the current thread's chain is the same instance this task belongs to.
|
|
354
|
-
#
|
|
355
|
-
# @rbs () -> void
|
|
356
|
-
def clear_chain!
|
|
357
|
-
return unless result.index.zero?
|
|
358
|
-
return unless Chain.current.equal?(task.chain)
|
|
359
|
-
|
|
360
|
-
Chain.clear
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
# Rolls back the work of a task.
|
|
364
|
-
#
|
|
365
|
-
# @rbs () -> void
|
|
366
|
-
def rollback_execution!
|
|
367
|
-
return if result.rolled_back?
|
|
368
|
-
return unless task.respond_to?(:rollback)
|
|
369
|
-
|
|
370
|
-
@rollback_statuses ||= Utils::Normalize.statuses(task.class.settings.rollback_on).freeze
|
|
371
|
-
return unless @rollback_statuses.include?(result.status)
|
|
372
|
-
|
|
373
|
-
result.rolled_back = true
|
|
374
|
-
task.rollback
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
end
|
|
378
|
-
end
|
data/lib/cmdx/identifier.rb
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module CMDx
|
|
4
|
-
# Generates unique identifiers for tasks, workflows, and other CMDx components.
|
|
5
|
-
#
|
|
6
|
-
# The Identifier module provides a consistent way to generate unique identifiers
|
|
7
|
-
# across the CMDx system, with fallback support for different Ruby versions.
|
|
8
|
-
module Identifier
|
|
9
|
-
|
|
10
|
-
extend self
|
|
11
|
-
|
|
12
|
-
# Generates a unique identifier string.
|
|
13
|
-
#
|
|
14
|
-
# @return [String] A unique identifier string (UUID v7 if available, otherwise UUID v4)
|
|
15
|
-
#
|
|
16
|
-
# @raise [StandardError] If SecureRandom is unavailable or fails to generate an identifier
|
|
17
|
-
#
|
|
18
|
-
# @example Generate a unique identifier
|
|
19
|
-
# CMDx::Identifier.generate
|
|
20
|
-
# # => "01890b2c-1234-5678-9abc-def123456789"
|
|
21
|
-
#
|
|
22
|
-
# @rbs () -> String
|
|
23
|
-
if SecureRandom.respond_to?(:uuid_v7)
|
|
24
|
-
def generate = SecureRandom.uuid_v7
|
|
25
|
-
else
|
|
26
|
-
def generate = SecureRandom.uuid
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
end
|
|
30
|
-
end
|