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.
- checksums.yaml +4 -4
- data/.cursor/prompts/docs.md +9 -0
- data/.cursor/prompts/rspec.md +21 -0
- data/.cursor/prompts/yardoc.md +13 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +29 -3
- data/README.md +2 -1
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +126 -60
- data/docs/basics/chain.md +190 -160
- data/docs/basics/context.md +242 -154
- data/docs/basics/setup.md +302 -32
- data/docs/callbacks.md +382 -119
- data/docs/configuration.md +211 -49
- data/docs/deprecation.md +245 -0
- data/docs/getting_started.md +161 -39
- data/docs/internationalization.md +590 -70
- data/docs/interruptions/exceptions.md +135 -118
- data/docs/interruptions/faults.md +152 -127
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +183 -120
- data/docs/middlewares.md +165 -392
- data/docs/outcomes/result.md +140 -112
- data/docs/outcomes/states.md +134 -99
- data/docs/outcomes/statuses.md +204 -146
- data/docs/parameters/coercions.md +251 -289
- data/docs/parameters/defaults.md +224 -169
- data/docs/parameters/definitions.md +289 -141
- data/docs/parameters/namespacing.md +250 -161
- data/docs/parameters/validations.md +247 -159
- data/docs/testing.md +196 -203
- data/docs/workflows.md +146 -101
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +39 -55
- data/lib/cmdx/callback_registry.rb +80 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +23 -116
- data/lib/cmdx/chain_serializer.rb +34 -146
- data/lib/cmdx/coercion.rb +57 -0
- data/lib/cmdx/coercion_registry.rb +113 -0
- data/lib/cmdx/coercions/array.rb +18 -36
- data/lib/cmdx/coercions/big_decimal.rb +21 -33
- data/lib/cmdx/coercions/boolean.rb +21 -40
- data/lib/cmdx/coercions/complex.rb +18 -31
- data/lib/cmdx/coercions/date.rb +20 -39
- data/lib/cmdx/coercions/date_time.rb +22 -39
- data/lib/cmdx/coercions/float.rb +19 -32
- data/lib/cmdx/coercions/hash.rb +22 -41
- data/lib/cmdx/coercions/integer.rb +20 -33
- data/lib/cmdx/coercions/rational.rb +20 -32
- data/lib/cmdx/coercions/string.rb +23 -31
- data/lib/cmdx/coercions/time.rb +24 -40
- data/lib/cmdx/coercions/virtual.rb +14 -31
- data/lib/cmdx/configuration.rb +101 -162
- data/lib/cmdx/context.rb +34 -166
- data/lib/cmdx/core_ext/hash.rb +42 -67
- data/lib/cmdx/core_ext/module.rb +35 -79
- data/lib/cmdx/core_ext/object.rb +63 -98
- data/lib/cmdx/correlator.rb +59 -154
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +153 -216
- data/lib/cmdx/fault.rb +68 -150
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -110
- data/lib/cmdx/lazy_struct.rb +110 -186
- data/lib/cmdx/log_formatters/json.rb +14 -40
- data/lib/cmdx/log_formatters/key_value.rb +14 -40
- data/lib/cmdx/log_formatters/line.rb +14 -48
- data/lib/cmdx/log_formatters/logstash.rb +14 -57
- data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
- data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
- data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
- data/lib/cmdx/log_formatters/raw.rb +19 -49
- data/lib/cmdx/logger.rb +22 -79
- data/lib/cmdx/logger_ansi.rb +31 -72
- data/lib/cmdx/logger_serializer.rb +74 -103
- data/lib/cmdx/middleware.rb +56 -60
- data/lib/cmdx/middleware_registry.rb +82 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +167 -183
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +37 -55
- data/lib/cmdx/parameter_registry.rb +65 -84
- data/lib/cmdx/parameter_serializer.rb +32 -76
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -259
- data/lib/cmdx/result_ansi.rb +28 -80
- data/lib/cmdx/result_inspector.rb +34 -70
- data/lib/cmdx/result_logger.rb +23 -77
- data/lib/cmdx/result_serializer.rb +59 -125
- data/lib/cmdx/rspec/matchers.rb +28 -0
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
- data/lib/cmdx/task.rb +336 -427
- data/lib/cmdx/task_deprecator.rb +52 -0
- data/lib/cmdx/task_processor.rb +246 -0
- data/lib/cmdx/task_serializer.rb +34 -69
- data/lib/cmdx/utils/ansi_color.rb +13 -89
- data/lib/cmdx/utils/log_timestamp.rb +13 -42
- data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +57 -0
- data/lib/cmdx/validator_registry.rb +108 -0
- data/lib/cmdx/validators/exclusion.rb +55 -94
- data/lib/cmdx/validators/format.rb +31 -85
- data/lib/cmdx/validators/inclusion.rb +65 -110
- data/lib/cmdx/validators/length.rb +117 -133
- data/lib/cmdx/validators/numeric.rb +123 -130
- data/lib/cmdx/validators/presence.rb +38 -79
- data/lib/cmdx/version.rb +1 -7
- data/lib/cmdx/workflow.rb +58 -330
- data/lib/cmdx.rb +1 -1
- data/lib/generators/cmdx/install_generator.rb +14 -31
- data/lib/generators/cmdx/task_generator.rb +39 -55
- data/lib/generators/cmdx/templates/install.rb +24 -6
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +0 -1
- data/lib/locales/cs.yml +0 -1
- data/lib/locales/da.yml +0 -1
- data/lib/locales/de.yml +0 -1
- data/lib/locales/el.yml +0 -1
- data/lib/locales/en.yml +0 -1
- data/lib/locales/es.yml +0 -1
- data/lib/locales/fi.yml +0 -1
- data/lib/locales/fr.yml +0 -1
- data/lib/locales/he.yml +0 -1
- data/lib/locales/hi.yml +0 -1
- data/lib/locales/it.yml +0 -1
- data/lib/locales/ja.yml +0 -1
- data/lib/locales/ko.yml +0 -1
- data/lib/locales/nl.yml +0 -1
- data/lib/locales/no.yml +0 -1
- data/lib/locales/pl.yml +0 -1
- data/lib/locales/pt.yml +0 -1
- data/lib/locales/ru.yml +0 -1
- data/lib/locales/sv.yml +0 -1
- data/lib/locales/th.yml +0 -1
- data/lib/locales/tr.yml +0 -1
- data/lib/locales/vi.yml +0 -1
- data/lib/locales/zh.yml +0 -1
- metadata +36 -8
- data/lib/cmdx/parameter_validator.rb +0 -81
- data/lib/cmdx/parameter_value.rb +0 -244
- data/lib/cmdx/parameters_inspector.rb +0 -72
- data/lib/cmdx/parameters_serializer.rb +0 -115
- data/lib/cmdx/rspec/result_matchers.rb +0 -917
- data/lib/cmdx/rspec/task_matchers.rb +0 -570
- data/lib/cmdx/validators/custom.rb +0 -102
data/lib/cmdx/logger_ansi.rb
CHANGED
@@ -1,41 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
# ANSI color formatting
|
4
|
+
# ANSI color formatting for logger severity levels and text output.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
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
|
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
|
-
#
|
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
|
-
# @
|
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
|
59
|
-
# LoggerAnsi.call("DEBUG")
|
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
|
66
|
-
# LoggerAnsi.call("
|
36
|
+
# @example Format error severity text
|
37
|
+
# LoggerAnsi.call("ERROR: Operation failed") #=> "\e[1;31;49mERROR: Operation failed\e[0m"
|
67
38
|
#
|
68
|
-
# @example
|
69
|
-
#
|
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
|
45
|
+
# Determines the appropriate color for text based on its severity indicator.
|
79
46
|
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
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]
|
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
|
-
# @
|
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
|
95
|
-
# color("
|
96
|
-
# color("TRACE") # => :default
|
55
|
+
# @example Get color for debug severity
|
56
|
+
# LoggerAnsi.color("DEBUG: Message") #=> :blue
|
97
57
|
#
|
98
|
-
# @example
|
99
|
-
# color("
|
100
|
-
# color("Debug") # => :blue (uppercase 'D' is mapped)
|
58
|
+
# @example Get color for error severity
|
59
|
+
# LoggerAnsi.color("ERROR: Failed") #=> :red
|
101
60
|
#
|
102
|
-
# @
|
103
|
-
#
|
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
|
4
|
+
# Logger serialization module for converting messages and task data into structured log format.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
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
|
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
|
-
#
|
65
|
-
#
|
66
|
-
#
|
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
|
-
# @
|
69
|
-
# @
|
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
|
77
|
-
#
|
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: "
|
59
|
+
# # chain_id: "abc123",
|
82
60
|
# # type: "Task",
|
83
|
-
# # class: "
|
84
|
-
# # id: "
|
61
|
+
# # class: "ProcessDataTask",
|
62
|
+
# # id: "def456",
|
85
63
|
# # tags: [],
|
86
|
-
# #
|
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
|
90
|
-
#
|
91
|
-
#
|
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: "
|
77
|
+
# # chain_id: "abc123",
|
103
78
|
# # type: "Task",
|
104
79
|
# # class: "MyTask",
|
105
|
-
# # id: "
|
106
|
-
# # tags: []
|
80
|
+
# # id: "def456",
|
81
|
+
# # tags: [],
|
82
|
+
# # message: "Processing started"
|
107
83
|
# # }
|
108
84
|
#
|
109
|
-
# @example
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
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
|
-
# #
|
120
|
-
# #
|
121
|
-
# # index: 0,
|
122
|
-
# # chain_id: "...",
|
90
|
+
# # index: 1,
|
91
|
+
# # chain_id: "xyz789",
|
123
92
|
# # type: "Task",
|
124
|
-
# # class: "
|
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(
|
129
|
-
m = message.
|
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"
|
data/lib/cmdx/middleware.rb
CHANGED
@@ -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
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
# @
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
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
|