cmdx 1.20.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.
Files changed (195) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +131 -1
  3. data/README.md +37 -24
  4. data/lib/cmdx/.DS_Store +0 -0
  5. data/lib/cmdx/callbacks.rb +179 -0
  6. data/lib/cmdx/chain.rb +78 -175
  7. data/lib/cmdx/coercions/array.rb +19 -33
  8. data/lib/cmdx/coercions/big_decimal.rb +12 -29
  9. data/lib/cmdx/coercions/boolean.rb +25 -45
  10. data/lib/cmdx/coercions/coerce.rb +32 -0
  11. data/lib/cmdx/coercions/complex.rb +12 -27
  12. data/lib/cmdx/coercions/date.rb +29 -33
  13. data/lib/cmdx/coercions/date_time.rb +29 -33
  14. data/lib/cmdx/coercions/float.rb +8 -29
  15. data/lib/cmdx/coercions/hash.rb +17 -43
  16. data/lib/cmdx/coercions/integer.rb +8 -32
  17. data/lib/cmdx/coercions/rational.rb +12 -33
  18. data/lib/cmdx/coercions/string.rb +6 -24
  19. data/lib/cmdx/coercions/symbol.rb +12 -26
  20. data/lib/cmdx/coercions/time.rb +31 -35
  21. data/lib/cmdx/coercions.rb +174 -0
  22. data/lib/cmdx/configuration.rb +45 -225
  23. data/lib/cmdx/context.rb +263 -242
  24. data/lib/cmdx/deprecation.rb +67 -0
  25. data/lib/cmdx/deprecators/error.rb +22 -0
  26. data/lib/cmdx/deprecators/log.rb +22 -0
  27. data/lib/cmdx/deprecators/warn.rb +21 -0
  28. data/lib/cmdx/deprecators.rb +101 -0
  29. data/lib/cmdx/errors.rb +145 -79
  30. data/lib/cmdx/executors/fiber.rb +42 -0
  31. data/lib/cmdx/executors/thread.rb +36 -0
  32. data/lib/cmdx/executors.rb +95 -0
  33. data/lib/cmdx/fault.rb +85 -78
  34. data/lib/cmdx/i18n_proxy.rb +104 -0
  35. data/lib/cmdx/input.rb +294 -0
  36. data/lib/cmdx/inputs.rb +218 -0
  37. data/lib/cmdx/log_formatters/json.rb +9 -20
  38. data/lib/cmdx/log_formatters/key_value.rb +10 -21
  39. data/lib/cmdx/log_formatters/line.rb +7 -19
  40. data/lib/cmdx/log_formatters/logstash.rb +8 -21
  41. data/lib/cmdx/log_formatters/raw.rb +8 -20
  42. data/lib/cmdx/logger_proxy.rb +30 -0
  43. data/lib/cmdx/mergers/deep_merge.rb +23 -0
  44. data/lib/cmdx/mergers/last_write_wins.rb +23 -0
  45. data/lib/cmdx/mergers/no_merge.rb +20 -0
  46. data/lib/cmdx/mergers.rb +95 -0
  47. data/lib/cmdx/middlewares.rb +128 -0
  48. data/lib/cmdx/output.rb +115 -0
  49. data/lib/cmdx/outputs.rb +66 -0
  50. data/lib/cmdx/pipeline.rb +144 -131
  51. data/lib/cmdx/railtie.rb +10 -36
  52. data/lib/cmdx/result.rb +252 -473
  53. data/lib/cmdx/retriers/bounded_random.rb +24 -0
  54. data/lib/cmdx/retriers/decorrelated_jitter.rb +28 -0
  55. data/lib/cmdx/retriers/exponential.rb +23 -0
  56. data/lib/cmdx/retriers/fibonacci.rb +39 -0
  57. data/lib/cmdx/retriers/full_random.rb +23 -0
  58. data/lib/cmdx/retriers/half_random.rb +24 -0
  59. data/lib/cmdx/retriers/linear.rb +23 -0
  60. data/lib/cmdx/retriers.rb +106 -0
  61. data/lib/cmdx/retry.rb +117 -138
  62. data/lib/cmdx/runtime.rb +251 -0
  63. data/lib/cmdx/settings.rb +68 -196
  64. data/lib/cmdx/signal.rb +165 -0
  65. data/lib/cmdx/task.rb +443 -336
  66. data/lib/cmdx/telemetry.rb +108 -0
  67. data/lib/cmdx/util.rb +73 -0
  68. data/lib/cmdx/validators/absence.rb +10 -39
  69. data/lib/cmdx/validators/exclusion.rb +33 -52
  70. data/lib/cmdx/validators/format.rb +19 -49
  71. data/lib/cmdx/validators/inclusion.rb +33 -54
  72. data/lib/cmdx/validators/length.rb +125 -127
  73. data/lib/cmdx/validators/numeric.rb +123 -123
  74. data/lib/cmdx/validators/presence.rb +10 -39
  75. data/lib/cmdx/validators/validate.rb +31 -0
  76. data/lib/cmdx/validators.rb +161 -0
  77. data/lib/cmdx/version.rb +2 -4
  78. data/lib/cmdx/workflow.rb +74 -82
  79. data/lib/cmdx.rb +111 -42
  80. data/lib/generators/cmdx/install_generator.rb +7 -17
  81. data/lib/generators/cmdx/task_generator.rb +12 -29
  82. data/lib/generators/cmdx/templates/install.rb +128 -52
  83. data/lib/generators/cmdx/templates/task.rb.tt +1 -1
  84. data/lib/generators/cmdx/templates/workflow.rb.tt +1 -2
  85. data/lib/generators/cmdx/workflow_generator.rb +12 -29
  86. data/lib/locales/en.yml +9 -6
  87. data/mkdocs.yml +25 -23
  88. metadata +39 -138
  89. data/lib/cmdx/attribute.rb +0 -440
  90. data/lib/cmdx/attribute_registry.rb +0 -185
  91. data/lib/cmdx/attribute_value.rb +0 -252
  92. data/lib/cmdx/callback_registry.rb +0 -169
  93. data/lib/cmdx/coercion_registry.rb +0 -138
  94. data/lib/cmdx/deprecator.rb +0 -77
  95. data/lib/cmdx/exception.rb +0 -46
  96. data/lib/cmdx/executor.rb +0 -374
  97. data/lib/cmdx/identifier.rb +0 -30
  98. data/lib/cmdx/locale.rb +0 -78
  99. data/lib/cmdx/middleware_registry.rb +0 -148
  100. data/lib/cmdx/middlewares/correlate.rb +0 -140
  101. data/lib/cmdx/middlewares/runtime.rb +0 -62
  102. data/lib/cmdx/middlewares/timeout.rb +0 -78
  103. data/lib/cmdx/parallelizer.rb +0 -100
  104. data/lib/cmdx/utils/call.rb +0 -53
  105. data/lib/cmdx/utils/condition.rb +0 -71
  106. data/lib/cmdx/utils/format.rb +0 -82
  107. data/lib/cmdx/utils/normalize.rb +0 -52
  108. data/lib/cmdx/utils/wrap.rb +0 -38
  109. data/lib/cmdx/validator_registry.rb +0 -143
  110. data/lib/generators/cmdx/locale_generator.rb +0 -39
  111. data/lib/locales/af.yml +0 -53
  112. data/lib/locales/ar.yml +0 -53
  113. data/lib/locales/az.yml +0 -53
  114. data/lib/locales/be.yml +0 -53
  115. data/lib/locales/bg.yml +0 -53
  116. data/lib/locales/bn.yml +0 -53
  117. data/lib/locales/bs.yml +0 -53
  118. data/lib/locales/ca.yml +0 -53
  119. data/lib/locales/cnr.yml +0 -53
  120. data/lib/locales/cs.yml +0 -53
  121. data/lib/locales/cy.yml +0 -53
  122. data/lib/locales/da.yml +0 -53
  123. data/lib/locales/de.yml +0 -53
  124. data/lib/locales/dz.yml +0 -53
  125. data/lib/locales/el.yml +0 -53
  126. data/lib/locales/eo.yml +0 -53
  127. data/lib/locales/es.yml +0 -53
  128. data/lib/locales/et.yml +0 -53
  129. data/lib/locales/eu.yml +0 -53
  130. data/lib/locales/fa.yml +0 -53
  131. data/lib/locales/fi.yml +0 -53
  132. data/lib/locales/fr.yml +0 -53
  133. data/lib/locales/fy.yml +0 -53
  134. data/lib/locales/gd.yml +0 -53
  135. data/lib/locales/gl.yml +0 -53
  136. data/lib/locales/he.yml +0 -53
  137. data/lib/locales/hi.yml +0 -53
  138. data/lib/locales/hr.yml +0 -53
  139. data/lib/locales/hu.yml +0 -53
  140. data/lib/locales/hy.yml +0 -53
  141. data/lib/locales/id.yml +0 -53
  142. data/lib/locales/is.yml +0 -53
  143. data/lib/locales/it.yml +0 -53
  144. data/lib/locales/ja.yml +0 -53
  145. data/lib/locales/ka.yml +0 -53
  146. data/lib/locales/kk.yml +0 -53
  147. data/lib/locales/km.yml +0 -53
  148. data/lib/locales/kn.yml +0 -53
  149. data/lib/locales/ko.yml +0 -53
  150. data/lib/locales/lb.yml +0 -53
  151. data/lib/locales/lo.yml +0 -53
  152. data/lib/locales/lt.yml +0 -53
  153. data/lib/locales/lv.yml +0 -53
  154. data/lib/locales/mg.yml +0 -53
  155. data/lib/locales/mk.yml +0 -53
  156. data/lib/locales/ml.yml +0 -53
  157. data/lib/locales/mn.yml +0 -53
  158. data/lib/locales/mr-IN.yml +0 -53
  159. data/lib/locales/ms.yml +0 -53
  160. data/lib/locales/nb.yml +0 -53
  161. data/lib/locales/ne.yml +0 -53
  162. data/lib/locales/nl.yml +0 -53
  163. data/lib/locales/nn.yml +0 -53
  164. data/lib/locales/oc.yml +0 -53
  165. data/lib/locales/or.yml +0 -53
  166. data/lib/locales/pa.yml +0 -53
  167. data/lib/locales/pl.yml +0 -53
  168. data/lib/locales/pt.yml +0 -53
  169. data/lib/locales/rm.yml +0 -53
  170. data/lib/locales/ro.yml +0 -53
  171. data/lib/locales/ru.yml +0 -53
  172. data/lib/locales/sc.yml +0 -53
  173. data/lib/locales/sk.yml +0 -53
  174. data/lib/locales/sl.yml +0 -53
  175. data/lib/locales/sq.yml +0 -53
  176. data/lib/locales/sr.yml +0 -53
  177. data/lib/locales/st.yml +0 -53
  178. data/lib/locales/sv.yml +0 -53
  179. data/lib/locales/sw.yml +0 -53
  180. data/lib/locales/ta.yml +0 -53
  181. data/lib/locales/te.yml +0 -53
  182. data/lib/locales/th.yml +0 -53
  183. data/lib/locales/tl.yml +0 -53
  184. data/lib/locales/tr.yml +0 -53
  185. data/lib/locales/tt.yml +0 -53
  186. data/lib/locales/ug.yml +0 -53
  187. data/lib/locales/uk.yml +0 -53
  188. data/lib/locales/ur.yml +0 -53
  189. data/lib/locales/uz.yml +0 -53
  190. data/lib/locales/vi.yml +0 -53
  191. data/lib/locales/wo.yml +0 -53
  192. data/lib/locales/zh-CN.yml +0 -53
  193. data/lib/locales/zh-HK.yml +0 -53
  194. data/lib/locales/zh-TW.yml +0 -53
  195. data/lib/locales/zh-YUE.yml +0 -53
@@ -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
@@ -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
@@ -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,374 +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
- #
139
- # @param exception [Exception] The exception that occurred
140
- #
141
- # @return [Boolean] Whether execution should halt
142
- #
143
- # @rbs (Exception exception) -> bool
144
- def halt_execution?(exception)
145
- @halt_statuses ||= Utils::Normalize.statuses(
146
- task.class.settings.breakpoints ||
147
- task.class.settings.task_breakpoints
148
- ).freeze
149
-
150
- @halt_statuses.include?(exception.result.status)
151
- end
152
-
153
- # Determines if execution should be retried based on retry configuration.
154
- #
155
- # @param exception [Exception] The exception that occurred
156
- #
157
- # @return [Boolean] Whether execution should be retried
158
- #
159
- # @rbs (Exception exception) -> bool
160
- def retry_execution?(exception)
161
- @retry ||= Retry.new(task)
162
-
163
- return false unless @retry.available? && @retry.remaining?
164
- return false unless @retry.exception?(exception)
165
-
166
- result.retries += 1
167
-
168
- task.logger.warn do
169
- reason = Utils::Normalize.exception(exception)
170
- task.to_h.merge!(reason:, remaining_retries: @retry.remaining)
171
- end
172
-
173
- task.errors.clear
174
-
175
- wait = @retry.wait
176
- sleep(wait) if wait.positive?
177
-
178
- true
179
- end
180
-
181
- # Raises an exception and clears the chain.
182
- #
183
- # @param exception [Exception] The exception to raise
184
- #
185
- # @raise [Exception] The provided exception
186
- #
187
- # @rbs (Exception exception) -> void
188
- def raise_exception(exception)
189
- Chain.clear
190
-
191
- raise(exception)
192
- end
193
-
194
- # Invokes callbacks of a specific type for the task.
195
- #
196
- # @param type [Symbol] The type of callback to invoke
197
- #
198
- # @return [void]
199
- #
200
- # @example
201
- # invoke_callbacks(:before_execution)
202
- #
203
- # @rbs (Symbol type) -> void
204
- def invoke_callbacks(type)
205
- task.class.settings.callbacks.invoke(type, task)
206
- end
207
-
208
- private
209
-
210
- # Performs pre-execution tasks including validation and attribute verification.
211
- #
212
- # @rbs () -> void
213
- def pre_execution!
214
- @pre_execution = true
215
-
216
- invoke_callbacks(:before_validation)
217
-
218
- task.class.settings.attributes.define_and_verify(task)
219
- return if task.errors.empty?
220
-
221
- result.fail!(
222
- Locale.t("cmdx.faults.invalid"),
223
- source: :validation,
224
- errors: {
225
- full_message: task.errors.to_s,
226
- messages: task.errors.to_h
227
- }
228
- )
229
- end
230
-
231
- # Executes the main task logic.
232
- #
233
- # @rbs () -> void
234
- def execution!
235
- invoke_callbacks(:before_execution)
236
-
237
- result.executing!
238
- task.work
239
- end
240
-
241
- # Verifies that all declared returns are present in the context after execution.
242
- #
243
- # @rbs () -> void
244
- def verify_context_returns!
245
- return unless result.success?
246
-
247
- returns = Utils::Wrap.array(task.class.settings.returns)
248
- missing = returns.reject { |name| task.context.key?(name) }
249
- return if missing.empty?
250
-
251
- missing.each { |name| task.errors.add(name, Locale.t("cmdx.returns.missing")) }
252
-
253
- result.fail!(
254
- Locale.t("cmdx.faults.invalid"),
255
- source: :context,
256
- errors: {
257
- full_message: task.errors.to_s,
258
- messages: task.errors.to_h
259
- }
260
- )
261
- end
262
-
263
- # Performs post-execution tasks including callback invocation.
264
- #
265
- # @rbs () -> void
266
- def post_execution!
267
- return if task.class.settings.callbacks.empty?
268
-
269
- invoke_callbacks(STATE_CALLBACKS[result.state])
270
- invoke_callbacks(:on_executed) if result.executed?
271
-
272
- invoke_callbacks(STATUS_CALLBACKS[result.status])
273
- invoke_callbacks(:on_good) if result.good?
274
- invoke_callbacks(:on_bad) if result.bad?
275
- end
276
-
277
- # Detects if middleware swallowed the block without yielding.
278
- # When this happens the result is still in the initialized state.
279
- #
280
- # @rbs () -> void
281
- def verify_middleware_yield!
282
- return unless result.initialized?
283
-
284
- result.fail!(
285
- Locale.t("cmdx.faults.invalid"),
286
- halt: false,
287
- source: :middleware
288
- )
289
- result.executed!
290
- end
291
-
292
- # Finalizes execution by freezing the task, logging results, and rolling back work.
293
- #
294
- # @rbs () -> void
295
- def finalize_execution!
296
- log_execution!
297
- log_backtrace! if task.class.settings.backtrace
298
-
299
- rollback_execution!
300
- freeze_execution!
301
- clear_chain!
302
- end
303
-
304
- # Logs the execution result at the configured log level.
305
- #
306
- # @rbs () -> void
307
- def log_execution!
308
- task.logger.info { result.to_h }
309
- end
310
-
311
- # Logs the backtrace of the exception if the task failed.
312
- #
313
- # @rbs () -> void
314
- def log_backtrace!
315
- return unless result.failed?
316
-
317
- exception = result.caused_failure&.cause
318
- return if exception.nil? || exception.is_a?(Fault)
319
-
320
- task.logger.error do
321
- Utils::Normalize.exception(exception) << "\n" <<
322
- if (cleaner = task.class.settings.backtrace_cleaner)
323
- cleaner.call(exception.backtrace).join("\n\t")
324
- else
325
- exception.full_message(highlight: false)
326
- end
327
- end
328
- end
329
-
330
- # Freezes the task and its associated objects to prevent modifications.
331
- #
332
- # @rbs () -> void
333
- def freeze_execution!
334
- # Stubbing on frozen objects is not allowed in most test environments.
335
- return unless CMDx.configuration.freeze_results
336
-
337
- task.freeze
338
- result.freeze
339
-
340
- # Freezing the context and chain can only be done once the outer-most
341
- # task has completed.
342
- return unless result.index.zero?
343
-
344
- task.context.freeze
345
- task.chain.freeze
346
- end
347
-
348
- # Clears the chain if the task is the outermost (top-level) task
349
- # and the current thread's chain is the same instance this task belongs to.
350
- #
351
- # @rbs () -> void
352
- def clear_chain!
353
- return unless result.index.zero?
354
- return unless Chain.current.equal?(task.chain)
355
-
356
- Chain.clear
357
- end
358
-
359
- # Rolls back the work of a task.
360
- #
361
- # @rbs () -> void
362
- def rollback_execution!
363
- return if result.rolled_back?
364
- return unless task.respond_to?(:rollback)
365
-
366
- @rollback_statuses ||= Utils::Normalize.statuses(task.class.settings.rollback_on).freeze
367
- return unless @rollback_statuses.include?(result.status)
368
-
369
- result.rolled_back = true
370
- task.rollback
371
- end
372
-
373
- end
374
- end
@@ -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