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.
Files changed (195) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +118 -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 -237
  23. data/lib/cmdx/context.rb +264 -243
  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 +247 -524
  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 -200
  64. data/lib/cmdx/signal.rb +165 -0
  65. data/lib/cmdx/task.rb +443 -343
  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 +71 -96
  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 +120 -48
  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 +8 -7
  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 -378
  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 -77
  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 -55
  112. data/lib/locales/ar.yml +0 -55
  113. data/lib/locales/az.yml +0 -55
  114. data/lib/locales/be.yml +0 -55
  115. data/lib/locales/bg.yml +0 -55
  116. data/lib/locales/bn.yml +0 -55
  117. data/lib/locales/bs.yml +0 -55
  118. data/lib/locales/ca.yml +0 -55
  119. data/lib/locales/cnr.yml +0 -55
  120. data/lib/locales/cs.yml +0 -55
  121. data/lib/locales/cy.yml +0 -55
  122. data/lib/locales/da.yml +0 -55
  123. data/lib/locales/de.yml +0 -55
  124. data/lib/locales/dz.yml +0 -55
  125. data/lib/locales/el.yml +0 -55
  126. data/lib/locales/eo.yml +0 -55
  127. data/lib/locales/es.yml +0 -55
  128. data/lib/locales/et.yml +0 -55
  129. data/lib/locales/eu.yml +0 -55
  130. data/lib/locales/fa.yml +0 -55
  131. data/lib/locales/fi.yml +0 -55
  132. data/lib/locales/fr.yml +0 -55
  133. data/lib/locales/fy.yml +0 -55
  134. data/lib/locales/gd.yml +0 -55
  135. data/lib/locales/gl.yml +0 -55
  136. data/lib/locales/he.yml +0 -55
  137. data/lib/locales/hi.yml +0 -55
  138. data/lib/locales/hr.yml +0 -55
  139. data/lib/locales/hu.yml +0 -55
  140. data/lib/locales/hy.yml +0 -55
  141. data/lib/locales/id.yml +0 -55
  142. data/lib/locales/is.yml +0 -55
  143. data/lib/locales/it.yml +0 -55
  144. data/lib/locales/ja.yml +0 -55
  145. data/lib/locales/ka.yml +0 -55
  146. data/lib/locales/kk.yml +0 -55
  147. data/lib/locales/km.yml +0 -55
  148. data/lib/locales/kn.yml +0 -55
  149. data/lib/locales/ko.yml +0 -55
  150. data/lib/locales/lb.yml +0 -55
  151. data/lib/locales/lo.yml +0 -55
  152. data/lib/locales/lt.yml +0 -55
  153. data/lib/locales/lv.yml +0 -55
  154. data/lib/locales/mg.yml +0 -55
  155. data/lib/locales/mk.yml +0 -55
  156. data/lib/locales/ml.yml +0 -55
  157. data/lib/locales/mn.yml +0 -55
  158. data/lib/locales/mr-IN.yml +0 -55
  159. data/lib/locales/ms.yml +0 -55
  160. data/lib/locales/nb.yml +0 -55
  161. data/lib/locales/ne.yml +0 -55
  162. data/lib/locales/nl.yml +0 -55
  163. data/lib/locales/nn.yml +0 -55
  164. data/lib/locales/oc.yml +0 -55
  165. data/lib/locales/or.yml +0 -55
  166. data/lib/locales/pa.yml +0 -55
  167. data/lib/locales/pl.yml +0 -55
  168. data/lib/locales/pt.yml +0 -55
  169. data/lib/locales/rm.yml +0 -55
  170. data/lib/locales/ro.yml +0 -55
  171. data/lib/locales/ru.yml +0 -55
  172. data/lib/locales/sc.yml +0 -55
  173. data/lib/locales/sk.yml +0 -55
  174. data/lib/locales/sl.yml +0 -55
  175. data/lib/locales/sq.yml +0 -55
  176. data/lib/locales/sr.yml +0 -55
  177. data/lib/locales/st.yml +0 -55
  178. data/lib/locales/sv.yml +0 -55
  179. data/lib/locales/sw.yml +0 -55
  180. data/lib/locales/ta.yml +0 -55
  181. data/lib/locales/te.yml +0 -55
  182. data/lib/locales/th.yml +0 -55
  183. data/lib/locales/tl.yml +0 -55
  184. data/lib/locales/tr.yml +0 -55
  185. data/lib/locales/tt.yml +0 -55
  186. data/lib/locales/ug.yml +0 -55
  187. data/lib/locales/uk.yml +0 -55
  188. data/lib/locales/ur.yml +0 -55
  189. data/lib/locales/uz.yml +0 -55
  190. data/lib/locales/vi.yml +0 -55
  191. data/lib/locales/wo.yml +0 -55
  192. data/lib/locales/zh-CN.yml +0 -55
  193. data/lib/locales/zh-HK.yml +0 -55
  194. data/lib/locales/zh-TW.yml +0 -55
  195. 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
@@ -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,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
@@ -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