cmdx 1.0.1 → 1.1.1

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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +21 -0
  4. data/.cursor/prompts/yardoc.md +13 -0
  5. data/.rubocop.yml +2 -0
  6. data/CHANGELOG.md +29 -3
  7. data/README.md +2 -1
  8. data/docs/ai_prompts.md +269 -195
  9. data/docs/basics/call.md +126 -60
  10. data/docs/basics/chain.md +190 -160
  11. data/docs/basics/context.md +242 -154
  12. data/docs/basics/setup.md +302 -32
  13. data/docs/callbacks.md +382 -119
  14. data/docs/configuration.md +211 -49
  15. data/docs/deprecation.md +245 -0
  16. data/docs/getting_started.md +161 -39
  17. data/docs/internationalization.md +590 -70
  18. data/docs/interruptions/exceptions.md +135 -118
  19. data/docs/interruptions/faults.md +152 -127
  20. data/docs/interruptions/halt.md +134 -80
  21. data/docs/logging.md +183 -120
  22. data/docs/middlewares.md +165 -392
  23. data/docs/outcomes/result.md +140 -112
  24. data/docs/outcomes/states.md +134 -99
  25. data/docs/outcomes/statuses.md +204 -146
  26. data/docs/parameters/coercions.md +251 -289
  27. data/docs/parameters/defaults.md +224 -169
  28. data/docs/parameters/definitions.md +289 -141
  29. data/docs/parameters/namespacing.md +250 -161
  30. data/docs/parameters/validations.md +247 -159
  31. data/docs/testing.md +196 -203
  32. data/docs/workflows.md +146 -101
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +39 -55
  35. data/lib/cmdx/callback_registry.rb +80 -73
  36. data/lib/cmdx/chain.rb +65 -122
  37. data/lib/cmdx/chain_inspector.rb +23 -116
  38. data/lib/cmdx/chain_serializer.rb +34 -146
  39. data/lib/cmdx/coercion.rb +57 -0
  40. data/lib/cmdx/coercion_registry.rb +113 -0
  41. data/lib/cmdx/coercions/array.rb +18 -36
  42. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  43. data/lib/cmdx/coercions/boolean.rb +21 -40
  44. data/lib/cmdx/coercions/complex.rb +18 -31
  45. data/lib/cmdx/coercions/date.rb +20 -39
  46. data/lib/cmdx/coercions/date_time.rb +22 -39
  47. data/lib/cmdx/coercions/float.rb +19 -32
  48. data/lib/cmdx/coercions/hash.rb +22 -41
  49. data/lib/cmdx/coercions/integer.rb +20 -33
  50. data/lib/cmdx/coercions/rational.rb +20 -32
  51. data/lib/cmdx/coercions/string.rb +23 -31
  52. data/lib/cmdx/coercions/time.rb +24 -40
  53. data/lib/cmdx/coercions/virtual.rb +14 -31
  54. data/lib/cmdx/configuration.rb +101 -162
  55. data/lib/cmdx/context.rb +34 -166
  56. data/lib/cmdx/core_ext/hash.rb +42 -67
  57. data/lib/cmdx/core_ext/module.rb +35 -79
  58. data/lib/cmdx/core_ext/object.rb +63 -98
  59. data/lib/cmdx/correlator.rb +59 -154
  60. data/lib/cmdx/error.rb +37 -202
  61. data/lib/cmdx/errors.rb +153 -216
  62. data/lib/cmdx/fault.rb +68 -150
  63. data/lib/cmdx/faults.rb +26 -137
  64. data/lib/cmdx/immutator.rb +22 -110
  65. data/lib/cmdx/lazy_struct.rb +110 -186
  66. data/lib/cmdx/log_formatters/json.rb +14 -40
  67. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  68. data/lib/cmdx/log_formatters/line.rb +14 -48
  69. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  70. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  71. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  72. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  73. data/lib/cmdx/log_formatters/raw.rb +19 -49
  74. data/lib/cmdx/logger.rb +22 -79
  75. data/lib/cmdx/logger_ansi.rb +31 -72
  76. data/lib/cmdx/logger_serializer.rb +74 -103
  77. data/lib/cmdx/middleware.rb +56 -60
  78. data/lib/cmdx/middleware_registry.rb +82 -77
  79. data/lib/cmdx/middlewares/correlate.rb +41 -226
  80. data/lib/cmdx/middlewares/timeout.rb +46 -185
  81. data/lib/cmdx/parameter.rb +167 -183
  82. data/lib/cmdx/parameter_evaluator.rb +231 -0
  83. data/lib/cmdx/parameter_inspector.rb +37 -55
  84. data/lib/cmdx/parameter_registry.rb +65 -84
  85. data/lib/cmdx/parameter_serializer.rb +32 -76
  86. data/lib/cmdx/railtie.rb +24 -107
  87. data/lib/cmdx/result.rb +254 -259
  88. data/lib/cmdx/result_ansi.rb +28 -80
  89. data/lib/cmdx/result_inspector.rb +34 -70
  90. data/lib/cmdx/result_logger.rb +23 -77
  91. data/lib/cmdx/result_serializer.rb +59 -125
  92. data/lib/cmdx/rspec/matchers.rb +28 -0
  93. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  94. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  95. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  96. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  97. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  98. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  99. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  100. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  101. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  102. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  103. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  104. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  105. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  106. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  107. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  108. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  109. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  110. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  111. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  112. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  113. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  114. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  115. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  116. data/lib/cmdx/task.rb +336 -427
  117. data/lib/cmdx/task_deprecator.rb +52 -0
  118. data/lib/cmdx/task_processor.rb +246 -0
  119. data/lib/cmdx/task_serializer.rb +34 -69
  120. data/lib/cmdx/utils/ansi_color.rb +13 -89
  121. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  122. data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
  123. data/lib/cmdx/utils/name_affix.rb +21 -71
  124. data/lib/cmdx/validator.rb +57 -0
  125. data/lib/cmdx/validator_registry.rb +108 -0
  126. data/lib/cmdx/validators/exclusion.rb +55 -94
  127. data/lib/cmdx/validators/format.rb +31 -85
  128. data/lib/cmdx/validators/inclusion.rb +65 -110
  129. data/lib/cmdx/validators/length.rb +117 -133
  130. data/lib/cmdx/validators/numeric.rb +123 -130
  131. data/lib/cmdx/validators/presence.rb +38 -79
  132. data/lib/cmdx/version.rb +1 -7
  133. data/lib/cmdx/workflow.rb +58 -330
  134. data/lib/cmdx.rb +1 -1
  135. data/lib/generators/cmdx/install_generator.rb +14 -31
  136. data/lib/generators/cmdx/task_generator.rb +39 -55
  137. data/lib/generators/cmdx/templates/install.rb +24 -6
  138. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  139. data/lib/locales/ar.yml +0 -1
  140. data/lib/locales/cs.yml +0 -1
  141. data/lib/locales/da.yml +0 -1
  142. data/lib/locales/de.yml +0 -1
  143. data/lib/locales/el.yml +0 -1
  144. data/lib/locales/en.yml +0 -1
  145. data/lib/locales/es.yml +0 -1
  146. data/lib/locales/fi.yml +0 -1
  147. data/lib/locales/fr.yml +0 -1
  148. data/lib/locales/he.yml +0 -1
  149. data/lib/locales/hi.yml +0 -1
  150. data/lib/locales/it.yml +0 -1
  151. data/lib/locales/ja.yml +0 -1
  152. data/lib/locales/ko.yml +0 -1
  153. data/lib/locales/nl.yml +0 -1
  154. data/lib/locales/no.yml +0 -1
  155. data/lib/locales/pl.yml +0 -1
  156. data/lib/locales/pt.yml +0 -1
  157. data/lib/locales/ru.yml +0 -1
  158. data/lib/locales/sv.yml +0 -1
  159. data/lib/locales/th.yml +0 -1
  160. data/lib/locales/tr.yml +0 -1
  161. data/lib/locales/vi.yml +0 -1
  162. data/lib/locales/zh.yml +0 -1
  163. metadata +36 -8
  164. data/lib/cmdx/parameter_validator.rb +0 -81
  165. data/lib/cmdx/parameter_value.rb +0 -244
  166. data/lib/cmdx/parameters_inspector.rb +0 -72
  167. data/lib/cmdx/parameters_serializer.rb +0 -115
  168. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  169. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  170. data/lib/cmdx/validators/custom.rb +0 -102
@@ -1,41 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # ANSI color formatting module for logger severity levels.
4
+ # ANSI color formatting for logger severity levels and text output.
5
5
  #
6
- # The LoggerAnsi module provides ANSI color formatting for log severity levels
7
- # to enhance readability in terminal output. Each severity level is assigned
8
- # a specific color and bold formatting to make log messages more visually
9
- # distinguishable.
10
- #
11
- # @example Basic severity colorization
12
- # LoggerAnsi.call("DEBUG message") # => Blue bold text
13
- # LoggerAnsi.call("INFO message") # => Green bold text
14
- # LoggerAnsi.call("WARN message") # => Yellow bold text
15
- # LoggerAnsi.call("ERROR message") # => Red bold text
16
- # LoggerAnsi.call("FATAL message") # => Magenta bold text
17
- #
18
- # @example Usage in log formatters
19
- # class CustomFormatter
20
- # def call(severity, time, progname, msg)
21
- # colored_severity = LoggerAnsi.call(severity)
22
- # "#{colored_severity} #{msg}"
23
- # end
24
- # end
25
- #
26
- # @example Integration with pretty formatters
27
- # # Used internally by PrettyLine, PrettyJson, PrettyKeyValue formatters
28
- # formatted_severity = LoggerAnsi.call("ERROR") # => Red bold "ERROR"
29
- #
30
- # @see CMDx::Utils::AnsiColor ANSI color utility functions
31
- # @see CMDx::LogFormatters::PrettyLine Pretty line formatter with colors
32
- # @see CMDx::LogFormatters::PrettyJson Pretty JSON formatter with colors
6
+ # LoggerAnsi provides utility methods for applying ANSI color codes to logger
7
+ # severity indicators and general text formatting. It maps standard logger
8
+ # severity levels to appropriate colors for enhanced readability in terminal output,
9
+ # delegating actual color application to the AnsiColor utility module.
33
10
  module LoggerAnsi
34
11
 
35
- # Mapping of log severity levels to ANSI colors.
36
- #
37
- # Maps the first character of severity levels to their corresponding
38
- # color codes for consistent visual representation across log output.
39
12
  SEVERITY_COLORS = {
40
13
  "D" => :blue, # DEBUG
41
14
  "I" => :green, # INFO
@@ -46,61 +19,47 @@ module CMDx
46
19
 
47
20
  module_function
48
21
 
49
- # Applies ANSI color formatting to a severity string.
22
+ # Applies ANSI color formatting to text based on severity level indication.
23
+ #
24
+ # This method extracts the color for the given text based on its first character
25
+ # (typically a severity indicator) and applies both the determined color and bold
26
+ # formatting using the AnsiColor utility. The method provides consistent color
27
+ # formatting for logger output across the CMDx framework.
50
28
  #
51
- # Formats the input string with appropriate ANSI color codes based on
52
- # the first character of the string, which typically represents the
53
- # log severity level. All formatted text is rendered in bold.
29
+ # @param s [String] the text to format, typically starting with a severity indicator
54
30
  #
55
- # @param s [String] The severity string to colorize
56
- # @return [String] The string with ANSI color codes applied
31
+ # @return [String] the formatted text with ANSI color and bold styling applied
57
32
  #
58
- # @example Colorizing different severity levels
59
- # LoggerAnsi.call("DEBUG") # => "\e[1;34mDEBUG\e[0m" (blue bold)
60
- # LoggerAnsi.call("INFO") # => "\e[1;32mINFO\e[0m" (green bold)
61
- # LoggerAnsi.call("WARN") # => "\e[1;33mWARN\e[0m" (yellow bold)
62
- # LoggerAnsi.call("ERROR") # => "\e[1;31mERROR\e[0m" (red bold)
63
- # LoggerAnsi.call("FATAL") # => "\e[1;35mFATAL\e[0m" (magenta bold)
33
+ # @example Format debug severity text
34
+ # LoggerAnsi.call("DEBUG: Starting process") #=> "\e[1;34;49mDEBUG: Starting process\e[0m"
64
35
  #
65
- # @example Unknown severity level
66
- # LoggerAnsi.call("CUSTOM") # => "\e[1;39mCUSTOM\e[0m" (default color bold)
36
+ # @example Format error severity text
37
+ # LoggerAnsi.call("ERROR: Operation failed") #=> "\e[1;31;49mERROR: Operation failed\e[0m"
67
38
  #
68
- # @example Full log message formatting
69
- # severity = "ERROR"
70
- # message = "Task failed with validation errors"
71
- # colored_severity = LoggerAnsi.call(severity)
72
- # log_line = "#{colored_severity}: #{message}"
73
- # # => "\e[1;31mERROR\e[0m: Task failed with validation errors"
39
+ # @example Format text with unknown severity
40
+ # LoggerAnsi.call("CUSTOM: Message") #=> "\e[1;39;49mCUSTOM: Message\e[0m"
74
41
  def call(s)
75
42
  Utils::AnsiColor.call(s, color: color(s), mode: :bold)
76
43
  end
77
44
 
78
- # Determines the appropriate color for a severity string.
45
+ # Determines the appropriate color for text based on its severity indicator.
79
46
  #
80
- # Extracts the first character from the severity string and maps it to
81
- # the corresponding color symbol using the SEVERITY_COLORS hash. If no
82
- # mapping is found for the first character, returns the default color.
47
+ # This method extracts the first character from the provided text and maps it
48
+ # to a corresponding color defined in SEVERITY_COLORS. If no matching severity
49
+ # is found, it returns the default color to ensure consistent formatting behavior.
83
50
  #
84
- # @param s [String] The severity string to determine color for
85
- # @return [Symbol] The color symbol for the severity level
51
+ # @param s [String] the text to analyze, typically starting with a severity indicator
86
52
  #
87
- # @example Mapping severity levels to colors
88
- # color("DEBUG") # => :blue
89
- # color("INFO") # => :green
90
- # color("WARN") # => :yellow
91
- # color("ERROR") # => :red
92
- # color("FATAL") # => :magenta
53
+ # @return [Symbol] the color symbol corresponding to the severity level, or :default if not found
93
54
  #
94
- # @example Unknown severity level
95
- # color("CUSTOM") # => :default
96
- # color("TRACE") # => :default
55
+ # @example Get color for debug severity
56
+ # LoggerAnsi.color("DEBUG: Message") #=> :blue
97
57
  #
98
- # @example Case sensitivity
99
- # color("debug") # => :default (lowercase 'd' not mapped)
100
- # color("Debug") # => :blue (uppercase 'D' is mapped)
58
+ # @example Get color for error severity
59
+ # LoggerAnsi.color("ERROR: Failed") #=> :red
101
60
  #
102
- # @note This method only considers the first character of the input string
103
- # @see SEVERITY_COLORS The mapping hash used for color determination
61
+ # @example Get color for unknown severity
62
+ # LoggerAnsi.color("UNKNOWN: Text") #=> :default
104
63
  def color(s)
105
64
  SEVERITY_COLORS[s[0]] || :default
106
65
  end
@@ -1,140 +1,111 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Logger message serialization module for structured log output.
4
+ # Logger serialization module for converting messages and task data into structured log format.
5
5
  #
6
- # The LoggerSerializer module provides functionality to serialize log messages
7
- # into structured hash format suitable for various log formatters. It handles
8
- # different message types including Result objects and plain messages, with
9
- # optional ANSI colorization for terminal output.
10
- #
11
- # @example Basic message serialization
12
- # task = ProcessOrderTask.new
13
- # message = "Processing order 123"
14
- #
15
- # LoggerSerializer.call(:info, Time.now, task, message)
16
- # # => {
17
- # # origin: "CMDx",
18
- # # index: 0,
19
- # # chain_id: "...",
20
- # # type: "Task",
21
- # # class: "ProcessOrderTask",
22
- # # id: "...",
23
- # # tags: [],
24
- # # message: "Processing order 123"
25
- # # }
26
- #
27
- # @example Result object serialization
28
- # result = task.result # CMDx::Result instance
29
- #
30
- # LoggerSerializer.call(:info, Time.now, task, result)
31
- # # => {
32
- # # origin: "CMDx",
33
- # # state: "complete",
34
- # # status: "success",
35
- # # outcome: "success",
36
- # # metadata: {},
37
- # # runtime: 0.5,
38
- # # index: 0,
39
- # # chain_id: "...",
40
- # # # ... other result data
41
- # # }
42
- #
43
- # @example Colorized result serialization
44
- # LoggerSerializer.call(:info, Time.now, task, result, ansi_colorize: true)
45
- # # => Same as above but with ANSI color codes in state/status/outcome values
46
- #
47
- # @see CMDx::Result Result object structure and data
48
- # @see CMDx::TaskSerializer Task serialization functionality
49
- # @see CMDx::ResultAnsi Result ANSI colorization
6
+ # LoggerSerializer provides functionality to serialize task execution messages into a
7
+ # standardized hash representation suitable for logging systems. It handles both result
8
+ # objects and arbitrary messages, applying consistent formatting with optional ANSI
9
+ # colorization for terminal output. The serializer intelligently processes different
10
+ # message types and enriches log data with task metadata and origin information.
50
11
  module LoggerSerializer
51
12
 
52
- # Keys that should be colorized when ANSI colorization is enabled.
53
- #
54
- # These keys represent result state information that benefits from
55
- # color coding in terminal output for better visual distinction.
56
13
  COLORED_KEYS = %i[
57
14
  state status outcome
58
15
  ].freeze
59
16
 
60
17
  module_function
61
18
 
62
- # Serializes a log message into a structured hash format.
19
+ # Serializes a log message with task context into structured hash format.
20
+ #
21
+ # Converts log messages into a standardized hash representation suitable for
22
+ # various logging systems and output formats. When the message is a Result object,
23
+ # it extracts the result's hash representation and optionally applies ANSI colors
24
+ # to specific keys for enhanced terminal visibility. For non-result messages,
25
+ # it enriches the log entry with task metadata from TaskSerializer. All log
26
+ # entries are tagged with CMDx origin for source identification.
27
+ #
28
+ # @param severity [Symbol] the log severity level (not used in current implementation)
29
+ # @param time [Time] the timestamp of the log entry (not used in current implementation)
30
+ # @param task [CMDx::Task, CMDx::Workflow] the task or workflow instance providing context
31
+ # @param message [CMDx::Result, Object] the primary message content to serialize
32
+ # @param options [Hash] additional options for serialization behavior
33
+ # @option options [Boolean] :ansi_colorize whether to apply ANSI colors to result keys
63
34
  #
64
- # Converts log messages into hash format suitable for structured logging.
65
- # Handles both Result objects and plain messages differently, with optional
66
- # ANSI colorization for terminal-friendly output.
35
+ # @return [Hash] a structured hash containing the serialized log message and metadata
36
+ # @option return [String] :origin always set to "CMDx" for source identification
37
+ # @option return [Integer] :index the task's position index in the execution chain (when message is not Result)
38
+ # @option return [String] :chain_id the unique identifier of the task's execution chain (when message is not Result)
39
+ # @option return [String] :type the task type, either "Task" or "Workflow" (when message is not Result)
40
+ # @option return [String] :class the full class name of the task (when message is not Result)
41
+ # @option return [String] :id the unique identifier of the task instance (when message is not Result)
42
+ # @option return [Array] :tags the tags associated with the task from cmd settings (when message is not Result)
43
+ # @option return [Object] :message the original message content (when message is not Result)
44
+ # @option return [Symbol] :state the execution state with optional ANSI colors (when message is Result)
45
+ # @option return [Symbol] :status the execution status with optional ANSI colors (when message is Result)
46
+ # @option return [Symbol] :outcome the execution outcome with optional ANSI colors (when message is Result)
47
+ # @option return [Hash] :metadata additional metadata from result (when message is Result)
48
+ # @option return [Float] :runtime execution runtime in seconds (when message is Result)
67
49
  #
68
- # @param _severity [Symbol] Log severity level (not used in current implementation)
69
- # @param _time [Time] Log timestamp (not used in current implementation)
70
- # @param task [CMDx::Task] The task instance generating the log message
71
- # @param message [Object] The message to serialize (Result object or other)
72
- # @param options [Hash] Serialization options
73
- # @option options [Boolean] :ansi_colorize (false) Whether to apply ANSI colors
74
- # @return [Hash] Structured hash representation of the log message
50
+ # @raise [NoMethodError] if task doesn't respond to required methods for TaskSerializer
51
+ # @raise [NoMethodError] if result message doesn't respond to to_h method
75
52
  #
76
- # @example Plain message serialization
77
- # LoggerSerializer.call(:info, Time.now, task, "Task started")
78
- # # => {
53
+ # @example Serialize a result message with ANSI colors
54
+ # task = ProcessDataTask.call(data: "test")
55
+ # LoggerSerializer.call(:info, Time.now, task, task.result, ansi_colorize: true)
56
+ # #=> {
79
57
  # # origin: "CMDx",
80
58
  # # index: 0,
81
- # # chain_id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
59
+ # # chain_id: "abc123",
82
60
  # # type: "Task",
83
- # # class: "MyTask",
84
- # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
61
+ # # class: "ProcessDataTask",
62
+ # # id: "def456",
85
63
  # # tags: [],
86
- # # message: "Task started"
64
+ # # state: "\e[0;32;49mcomplete\e[0m",
65
+ # # status: "\e[0;32;49msuccess\e[0m",
66
+ # # outcome: "\e[0;32;49mgood\e[0m",
67
+ # # metadata: {},
68
+ # # runtime: 0.045
87
69
  # # }
88
70
  #
89
- # @example Result object serialization
90
- # result = CMDx::Result.new(task)
91
- # result.complete!
92
- #
93
- # LoggerSerializer.call(:info, Time.now, task, result)
94
- # # => {
71
+ # @example Serialize a string message with task context
72
+ # task = MyTask.new(context: {data: "test"})
73
+ # LoggerSerializer.call(:warn, Time.now, task, "Processing started")
74
+ # #=> {
95
75
  # # origin: "CMDx",
96
- # # state: "complete",
97
- # # status: "success",
98
- # # outcome: "success",
99
- # # metadata: {},
100
- # # runtime: 0.001,
101
76
  # # index: 0,
102
- # # chain_id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
77
+ # # chain_id: "abc123",
103
78
  # # type: "Task",
104
79
  # # class: "MyTask",
105
- # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
106
- # # tags: []
80
+ # # id: "def456",
81
+ # # tags: [],
82
+ # # message: "Processing started"
107
83
  # # }
108
84
  #
109
- # @example Colorized result serialization
110
- # LoggerSerializer.call(:info, Time.now, task, result, ansi_colorize: true)
111
- # # => Same as above but state/status/outcome values contain ANSI color codes
112
- # # => { state: "\e[32mcomplete\e[0m", status: "\e[32msuccess\e[0m", ... }
113
- #
114
- # @example Hash-like message object
115
- # custom_message = OpenStruct.new(action: "process", item_id: 123)
116
- # LoggerSerializer.call(:debug, Time.now, task, custom_message)
117
- # # => {
85
+ # @example Serialize a result message without colors
86
+ # task = ValidationTask.call(email: "invalid")
87
+ # LoggerSerializer.call(:error, Time.now, task, task.result)
88
+ # #=> {
118
89
  # # origin: "CMDx",
119
- # # action: "process",
120
- # # item_id: 123,
121
- # # index: 0,
122
- # # chain_id: "...",
90
+ # # index: 1,
91
+ # # chain_id: "xyz789",
123
92
  # # type: "Task",
124
- # # class: "MyTask",
125
- # # id: "...",
126
- # # tags: []
93
+ # # class: "ValidationTask",
94
+ # # id: "ghi012",
95
+ # # tags: [],
96
+ # # state: :interrupted,
97
+ # # status: :failed,
98
+ # # outcome: :bad,
99
+ # # metadata: { reason: "Invalid email format" },
100
+ # # runtime: 0.012
127
101
  # # }
128
- def call(_severity, _time, task, message, **options)
129
- m = message.respond_to?(:to_h) ? message.to_h : {}
102
+ def call(severity, time, task, message, **options) # rubocop:disable Lint/UnusedMethodArgument
103
+ m = message.is_a?(Result) ? message.to_h : {}
130
104
 
131
105
  if options.delete(:ansi_colorize) && message.is_a?(Result)
132
106
  COLORED_KEYS.each { |k| m[k] = ResultAnsi.call(m[k]) if m.key?(k) }
133
107
  elsif !message.is_a?(Result)
134
- m.merge!(
135
- TaskSerializer.call(task),
136
- message: message
137
- )
108
+ m.merge!(TaskSerializer.call(task), message: message)
138
109
  end
139
110
 
140
111
  m[:origin] ||= "CMDx"
@@ -1,71 +1,67 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- ##
5
- # Base class for CMDx middleware that follows Rack-style interface.
4
+ # Base class for implementing middleware functionality in task processing pipelines.
6
5
  #
7
- # Middleware components can wrap task execution to provide cross-cutting
8
- # concerns like logging, authentication, caching, or error handling.
9
- # Each middleware must implement the `call` method which receives the
10
- # task instance and a callable that represents the next middleware
11
- # in the chain.
12
- #
13
- # @example Basic middleware implementation
14
- # class LoggingMiddleware < CMDx::Middleware
15
- # def call(task, callable)
16
- # puts "Before executing #{task.class.name}"
17
- # result = callable.call(task)
18
- # puts "After executing #{task.class.name}"
19
- # result
20
- # end
21
- # end
22
- #
23
- # @example Middleware with initialization parameters
24
- # class AuthenticationMiddleware < CMDx::Middleware
25
- # def initialize(required_role)
26
- # @required_role = required_role
27
- # end
28
- #
29
- # def call(task, callable)
30
- # unless task.context.user&.has_role?(@required_role)
31
- # task.fail!(reason: "Insufficient permissions")
32
- # return task.result
33
- # end
34
- # callable.call(task)
35
- # end
36
- # end
37
- #
38
- # @example Short-circuiting middleware
39
- # class CachingMiddleware < CMDx::Middleware
40
- # def call(task, callable)
41
- # cache_key = "#{task.class.name}:#{task.context.to_h.hash}"
42
- #
43
- # if cached_result = Rails.cache.read(cache_key)
44
- # task.result.merge!(cached_result)
45
- # return task.result
46
- # end
47
- #
48
- # result = callable.call(task)
49
- # Rails.cache.write(cache_key, result.to_h) if result.success?
50
- # result
51
- # end
52
- # end
53
- #
54
- # @see MiddlewareRegistry management
55
- # @see Task middleware integration
6
+ # Middleware provides a way to wrap task execution with custom logic that runs before
7
+ # and after task processing. Middleware can be used for cross-cutting concerns such as
8
+ # logging, authentication, caching, error handling, and other aspects that should be
9
+ # applied consistently across multiple tasks. All middleware implementations must
10
+ # inherit from this class and implement the abstract call method.
56
11
  class Middleware
57
12
 
58
- ##
59
- # Executes the middleware logic.
13
+ # Executes middleware by creating a new instance and calling it.
14
+ #
15
+ # This class method provides a convenient way to execute middleware without
16
+ # manually instantiating the middleware class. It creates a new instance
17
+ # and delegates to the instance call method with the provided arguments.
18
+ #
19
+ # @param task [CMDx::Task] the task instance being processed
20
+ # @param callable [Proc] the callable that executes the next middleware or task logic
21
+ #
22
+ # @return [Object] the result returned by the middleware implementation
23
+ #
24
+ # @raise [UndefinedCallError] when the middleware subclass doesn't implement call
25
+ #
26
+ # @example Execute middleware on a task
27
+ # class LoggingMiddleware < CMDx::Middleware
28
+ # def call(task, callable)
29
+ # task.logger.info "Starting #{task.class.name}"
30
+ # result = callable.call
31
+ # task.logger.info "Completed #{task.class.name}"
32
+ # result
33
+ # end
34
+ # end
35
+ #
36
+ # LoggingMiddleware.call(my_task, -> { my_task.process })
37
+ def self.call(task, callable)
38
+ new.call(task, callable)
39
+ end
40
+
41
+ # Abstract method that must be implemented by middleware subclasses.
42
+ #
43
+ # This method contains the actual middleware logic that wraps task execution.
44
+ # Subclasses must override this method to provide their specific middleware
45
+ # implementation. The method should call the provided callable to continue
46
+ # the middleware chain or execute the task logic.
47
+ #
48
+ # @param _task [CMDx::Task] the task instance being processed
49
+ # @param _callable [Proc] the callable that executes the next middleware or task logic
50
+ #
51
+ # @return [Object] the result of the middleware processing
60
52
  #
61
- # This method must be implemented by subclasses to define the middleware
62
- # behavior. The method receives the task instance and a callable that
63
- # represents the next middleware in the chain or the final task execution.
53
+ # @raise [UndefinedCallError] always raised in the base class
64
54
  #
65
- # @param task [Task] the task instance being executed
66
- # @param callable [#call] the next middleware or task execution callable
67
- # @return [Result] the task execution result
68
- # @abstract Subclasses must implement this method
55
+ # @example Implement middleware in a subclass
56
+ # class TimingMiddleware < CMDx::Middleware
57
+ # def call(task, callable)
58
+ # start_time = Time.now
59
+ # result = callable.call
60
+ # duration = Time.now - start_time
61
+ # task.logger.info "Task completed in #{duration}s"
62
+ # result
63
+ # end
64
+ # end
69
65
  def call(_task, _callable)
70
66
  raise UndefinedCallError, "call method not defined in #{self.class.name}"
71
67
  end