cmdx 0.4.0 → 1.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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/rules/cursor-instructions.mdc +6 -0
  4. data/.rubocop.yml +16 -1
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +42 -1
  7. data/README.md +72 -25
  8. data/docs/ai_prompts.md +309 -0
  9. data/docs/basics/call.md +225 -14
  10. data/docs/basics/chain.md +271 -0
  11. data/docs/basics/context.md +232 -33
  12. data/docs/basics/setup.md +76 -12
  13. data/docs/callbacks.md +273 -0
  14. data/docs/configuration.md +158 -28
  15. data/docs/getting_started.md +134 -22
  16. data/docs/interruptions/exceptions.md +189 -11
  17. data/docs/interruptions/faults.md +187 -44
  18. data/docs/interruptions/halt.md +179 -35
  19. data/docs/logging.md +194 -53
  20. data/docs/middlewares.md +735 -0
  21. data/docs/outcomes/result.md +296 -10
  22. data/docs/outcomes/states.md +212 -19
  23. data/docs/outcomes/statuses.md +284 -18
  24. data/docs/parameters/coercions.md +402 -29
  25. data/docs/parameters/defaults.md +249 -25
  26. data/docs/parameters/definitions.md +238 -72
  27. data/docs/parameters/namespacing.md +250 -27
  28. data/docs/parameters/validations.md +193 -168
  29. data/docs/testing.md +550 -0
  30. data/docs/tips_and_tricks.md +95 -43
  31. data/docs/workflows.md +319 -0
  32. data/lib/cmdx/.DS_Store +0 -0
  33. data/lib/cmdx/callback.rb +69 -0
  34. data/lib/cmdx/callback_registry.rb +106 -0
  35. data/lib/cmdx/chain.rb +190 -0
  36. data/lib/cmdx/chain_inspector.rb +149 -0
  37. data/lib/cmdx/chain_serializer.rb +175 -0
  38. data/lib/cmdx/coercions/array.rb +37 -0
  39. data/lib/cmdx/coercions/big_decimal.rb +33 -0
  40. data/lib/cmdx/coercions/boolean.rb +41 -1
  41. data/lib/cmdx/coercions/complex.rb +31 -0
  42. data/lib/cmdx/coercions/date.rb +39 -0
  43. data/lib/cmdx/coercions/date_time.rb +39 -0
  44. data/lib/cmdx/coercions/float.rb +31 -0
  45. data/lib/cmdx/coercions/hash.rb +42 -0
  46. data/lib/cmdx/coercions/integer.rb +32 -0
  47. data/lib/cmdx/coercions/rational.rb +31 -0
  48. data/lib/cmdx/coercions/string.rb +31 -0
  49. data/lib/cmdx/coercions/time.rb +39 -0
  50. data/lib/cmdx/coercions/virtual.rb +31 -0
  51. data/lib/cmdx/configuration.rb +217 -9
  52. data/lib/cmdx/context.rb +173 -2
  53. data/lib/cmdx/core_ext/hash.rb +72 -0
  54. data/lib/cmdx/core_ext/module.rb +94 -0
  55. data/lib/cmdx/core_ext/object.rb +105 -0
  56. data/lib/cmdx/correlator.rb +217 -0
  57. data/lib/cmdx/error.rb +210 -8
  58. data/lib/cmdx/errors.rb +256 -1
  59. data/lib/cmdx/fault.rb +177 -2
  60. data/lib/cmdx/faults.rb +158 -2
  61. data/lib/cmdx/immutator.rb +121 -2
  62. data/lib/cmdx/lazy_struct.rb +261 -18
  63. data/lib/cmdx/log_formatters/json.rb +46 -0
  64. data/lib/cmdx/log_formatters/key_value.rb +46 -0
  65. data/lib/cmdx/log_formatters/line.rb +54 -0
  66. data/lib/cmdx/log_formatters/logstash.rb +64 -0
  67. data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
  68. data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
  69. data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
  70. data/lib/cmdx/log_formatters/raw.rb +54 -0
  71. data/lib/cmdx/logger.rb +85 -0
  72. data/lib/cmdx/logger_ansi.rb +93 -7
  73. data/lib/cmdx/logger_serializer.rb +116 -0
  74. data/lib/cmdx/middleware.rb +74 -0
  75. data/lib/cmdx/middleware_registry.rb +106 -0
  76. data/lib/cmdx/middlewares/correlate.rb +266 -0
  77. data/lib/cmdx/middlewares/timeout.rb +232 -0
  78. data/lib/cmdx/parameter.rb +228 -1
  79. data/lib/cmdx/parameter_inspector.rb +61 -0
  80. data/lib/cmdx/parameter_registry.rb +125 -0
  81. data/lib/cmdx/parameter_serializer.rb +83 -0
  82. data/lib/cmdx/parameter_validator.rb +62 -0
  83. data/lib/cmdx/parameter_value.rb +109 -1
  84. data/lib/cmdx/parameters_inspector.rb +59 -0
  85. data/lib/cmdx/parameters_serializer.rb +102 -0
  86. data/lib/cmdx/railtie.rb +123 -3
  87. data/lib/cmdx/result.rb +399 -20
  88. data/lib/cmdx/result_ansi.rb +105 -9
  89. data/lib/cmdx/result_inspector.rb +76 -0
  90. data/lib/cmdx/result_logger.rb +90 -3
  91. data/lib/cmdx/result_serializer.rb +137 -0
  92. data/lib/cmdx/rspec/result_matchers.rb +917 -0
  93. data/lib/cmdx/rspec/task_matchers.rb +570 -0
  94. data/lib/cmdx/task.rb +409 -34
  95. data/lib/cmdx/task_serializer.rb +74 -2
  96. data/lib/cmdx/utils/ansi_color.rb +95 -0
  97. data/lib/cmdx/utils/log_timestamp.rb +48 -0
  98. data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
  99. data/lib/cmdx/utils/name_affix.rb +78 -0
  100. data/lib/cmdx/validators/custom.rb +82 -0
  101. data/lib/cmdx/validators/exclusion.rb +94 -0
  102. data/lib/cmdx/validators/format.rb +102 -8
  103. data/lib/cmdx/validators/inclusion.rb +104 -0
  104. data/lib/cmdx/validators/length.rb +128 -0
  105. data/lib/cmdx/validators/numeric.rb +128 -0
  106. data/lib/cmdx/validators/presence.rb +93 -7
  107. data/lib/cmdx/version.rb +7 -1
  108. data/lib/cmdx/workflow.rb +394 -0
  109. data/lib/cmdx.rb +25 -64
  110. data/lib/generators/cmdx/install_generator.rb +37 -1
  111. data/lib/generators/cmdx/task_generator.rb +69 -1
  112. data/lib/generators/cmdx/templates/install.rb +8 -12
  113. data/lib/generators/cmdx/workflow_generator.rb +109 -0
  114. metadata +54 -15
  115. data/docs/basics/run.md +0 -34
  116. data/docs/batch.md +0 -53
  117. data/docs/example.md +0 -82
  118. data/docs/hooks.md +0 -59
  119. data/lib/cmdx/batch.rb +0 -43
  120. data/lib/cmdx/parameters.rb +0 -34
  121. data/lib/cmdx/run.rb +0 -38
  122. data/lib/cmdx/run_inspector.rb +0 -26
  123. data/lib/cmdx/run_serializer.rb +0 -16
  124. data/lib/cmdx/task_hook.rb +0 -18
  125. data/lib/generators/cmdx/batch_generator.rb +0 -30
  126. /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
@@ -1,26 +1,122 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
+ # ANSI color formatting module for result states and statuses.
5
+ #
6
+ # The ResultAnsi module provides ANSI color formatting for result state and
7
+ # status values to enhance readability in terminal output. It maps different
8
+ # result states and statuses to appropriate colors for visual distinction.
9
+ #
10
+ # @example Basic result colorization
11
+ # ResultAnsi.call("complete") # => Green colored text
12
+ # ResultAnsi.call("success") # => Green colored text
13
+ # ResultAnsi.call("failed") # => Red colored text
14
+ # ResultAnsi.call("interrupted") # => Red colored text
15
+ #
16
+ # @example Usage in log formatters
17
+ # result_data = { state: "complete", status: "success" }
18
+ # colored_state = ResultAnsi.call(result_data[:state])
19
+ # colored_status = ResultAnsi.call(result_data[:status])
20
+ #
21
+ # @example Integration with pretty formatters
22
+ # # Used internally by PrettyLine, PrettyJson, PrettyKeyValue formatters
23
+ # formatted_status = ResultAnsi.call("failed") # => Red "failed"
24
+ #
25
+ # @see CMDx::Result Result states and statuses
26
+ # @see CMDx::Utils::AnsiColor ANSI color utility functions
27
+ # @see CMDx::LogFormatters::PrettyLine Pretty line formatter with colors
4
28
  module ResultAnsi
5
29
 
30
+ # Mapping of result states to ANSI colors.
31
+ #
32
+ # Maps Result state constants to their corresponding color codes
33
+ # for consistent visual representation of execution states.
6
34
  STATE_COLORS = {
7
- Result::INITIALIZED => :blue,
8
- Result::EXECUTING => :yellow,
9
- Result::COMPLETE => :green,
10
- Result::INTERRUPTED => :red
35
+ Result::INITIALIZED => :blue, # Initial state - blue
36
+ Result::EXECUTING => :yellow, # Currently executing - yellow
37
+ Result::COMPLETE => :green, # Successfully completed - green
38
+ Result::INTERRUPTED => :red # Execution interrupted - red
11
39
  }.freeze
40
+
41
+ # Mapping of result statuses to ANSI colors.
42
+ #
43
+ # Maps Result status constants to their corresponding color codes
44
+ # for consistent visual representation of execution outcomes.
12
45
  STATUS_COLORS = {
13
- Result::SUCCESS => :green,
14
- Result::SKIPPED => :yellow,
15
- Result::FAILED => :red
46
+ Result::SUCCESS => :green, # Successful completion - green
47
+ Result::SKIPPED => :yellow, # Intentionally skipped - yellow
48
+ Result::FAILED => :red # Failed execution - red
16
49
  }.freeze
17
50
 
18
51
  module_function
19
52
 
53
+ # Applies ANSI color formatting to a result state or status string.
54
+ #
55
+ # Formats the input string with appropriate ANSI color codes based on
56
+ # whether it matches a known result state or status value. Falls back
57
+ # to default color for unknown values.
58
+ #
59
+ # @param s [String] The state or status string to colorize
60
+ # @return [String] The string with ANSI color codes applied
61
+ #
62
+ # @example Colorizing result states
63
+ # ResultAnsi.call("initialized") # => "\e[34minitialized\e[0m" (blue)
64
+ # ResultAnsi.call("executing") # => "\e[33mexecuting\e[0m" (yellow)
65
+ # ResultAnsi.call("complete") # => "\e[32mcomplete\e[0m" (green)
66
+ # ResultAnsi.call("interrupted") # => "\e[31minterrupted\e[0m" (red)
67
+ #
68
+ # @example Colorizing result statuses
69
+ # ResultAnsi.call("success") # => "\e[32msuccess\e[0m" (green)
70
+ # ResultAnsi.call("skipped") # => "\e[33mskipped\e[0m" (yellow)
71
+ # ResultAnsi.call("failed") # => "\e[31mfailed\e[0m" (red)
72
+ #
73
+ # @example Unknown value
74
+ # ResultAnsi.call("unknown") # => "\e[39munknown\e[0m" (default color)
75
+ #
76
+ # @example Usage in result formatting
77
+ # result = ProcessOrderTask.call
78
+ # colored_state = ResultAnsi.call(result.state)
79
+ # colored_status = ResultAnsi.call(result.status)
80
+ # puts "Task #{colored_state} with #{colored_status}"
81
+ # # => "Task complete with success" (with appropriate colors)
20
82
  def call(s)
21
- color = STATE_COLORS[s] || STATUS_COLORS[s] || :default
83
+ Utils::AnsiColor.call(s, color: color(s))
84
+ end
22
85
 
23
- Utils::AnsiColor.call(s, color:)
86
+ # Determines the appropriate color for a result state or status string.
87
+ #
88
+ # Looks up the input string in both the STATE_COLORS and STATUS_COLORS
89
+ # mapping hashes to find the corresponding color symbol. First checks
90
+ # STATE_COLORS, then STATUS_COLORS, and falls back to the default color
91
+ # if no mapping is found in either hash.
92
+ #
93
+ # @param s [String] The state or status string to determine color for
94
+ # @return [Symbol] The color symbol for the state or status
95
+ #
96
+ # @example Result state color mapping
97
+ # color("initialized") # => :blue
98
+ # color("executing") # => :yellow
99
+ # color("complete") # => :green
100
+ # color("interrupted") # => :red
101
+ #
102
+ # @example Result status color mapping
103
+ # color("success") # => :green
104
+ # color("skipped") # => :yellow
105
+ # color("failed") # => :red
106
+ #
107
+ # @example Unknown state or status
108
+ # color("unknown") # => :default
109
+ # color("pending") # => :default
110
+ #
111
+ # @example Precedence behavior
112
+ # # If a string exists in both hashes, STATE_COLORS takes precedence
113
+ # color("some_value") # => STATE_COLORS["some_value"] || STATUS_COLORS["some_value"] || :default
114
+ #
115
+ # @note STATE_COLORS mapping is checked before STATUS_COLORS mapping
116
+ # @see STATE_COLORS The mapping hash for result states
117
+ # @see STATUS_COLORS The mapping hash for result statuses
118
+ def color(s)
119
+ STATE_COLORS[s] || STATUS_COLORS[s] || :default
24
120
  end
25
121
 
26
122
  end
@@ -1,8 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
+ # Result inspection utility for generating human-readable result descriptions.
5
+ #
6
+ # The ResultInspector module provides functionality to convert result hash
7
+ # representations into formatted, human-readable strings. It handles special
8
+ # formatting for different result attributes and provides consistent ordering
9
+ # of result information.
10
+ #
11
+ # @example Basic result inspection
12
+ # result_hash = {
13
+ # class: "ProcessOrderTask",
14
+ # type: "Task",
15
+ # index: 0,
16
+ # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
17
+ # state: "complete",
18
+ # status: "success",
19
+ # outcome: "success",
20
+ # metadata: { order_id: 123 },
21
+ # runtime: 0.5
22
+ # }
23
+ #
24
+ # ResultInspector.call(result_hash)
25
+ # # => "ProcessOrderTask: type=Task index=0 id=018c2b95-b764-7615-a924-cc5b910ed1e5 state=complete status=success outcome=success metadata={order_id: 123} runtime=0.5"
26
+ #
27
+ # @example Result with failure information
28
+ # failed_result = {
29
+ # class: "ProcessOrderTask",
30
+ # type: "Task",
31
+ # index: 1,
32
+ # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
33
+ # state: "interrupted",
34
+ # status: "failed",
35
+ # outcome: "failed",
36
+ # caused_failure: { index: 0, class: "ValidationTask", id: "018c2b95..." },
37
+ # threw_failure: { index: 0, class: "ValidationTask", id: "018c2b95..." }
38
+ # }
39
+ #
40
+ # ResultInspector.call(failed_result)
41
+ # # => "ProcessOrderTask: type=Task index=1 id=018c2b95... state=interrupted status=failed outcome=failed caused_failure=<[0] ValidationTask: 018c2b95...> threw_failure=<[0] ValidationTask: 018c2b95...>"
42
+ #
43
+ # @see CMDx::Result Result hash serialization via to_h
44
+ # @see CMDx::ResultSerializer Result-to-hash conversion
4
45
  module ResultInspector
5
46
 
47
+ # Ordered keys for consistent result inspection output.
48
+ #
49
+ # Defines the order in which result attributes are displayed in the
50
+ # inspection string, ensuring consistent and logical presentation.
6
51
  ORDERED_KEYS = %i[
7
52
  class type index id state status outcome metadata
8
53
  tags pid runtime caused_failure threw_failure
@@ -10,6 +55,37 @@ module CMDx
10
55
 
11
56
  module_function
12
57
 
58
+ # Converts a result hash to a human-readable string representation.
59
+ #
60
+ # Formats result data into a structured string with proper ordering and
61
+ # special handling for different attribute types. The class name appears
62
+ # first followed by a colon, and failure references are specially formatted.
63
+ #
64
+ # @param result [Hash] The result hash to inspect
65
+ # @return [String] Formatted result description
66
+ #
67
+ # @example Simple result inspection
68
+ # ResultInspector.call(result_hash)
69
+ # # => "ProcessOrderTask: type=Task index=0 id=018c2b95... state=complete status=success"
70
+ #
71
+ # @example Result with metadata
72
+ # result_with_metadata = { class: "MyTask", metadata: { user_id: 123, action: "create" } }
73
+ # ResultInspector.call(result_with_metadata)
74
+ # # => "MyTask: metadata={user_id: 123, action: create}"
75
+ #
76
+ # @example Result with failure references
77
+ # result_with_failures = {
78
+ # class: "MainTask",
79
+ # caused_failure: { index: 2, class: "SubTask", id: "abc123" },
80
+ # threw_failure: { index: 1, class: "HelperTask", id: "def456" }
81
+ # }
82
+ # ResultInspector.call(result_with_failures)
83
+ # # => "MainTask: caused_failure=<[2] SubTask: abc123> threw_failure=<[1] HelperTask: def456>"
84
+ #
85
+ # @example Result with runtime information
86
+ # result_with_runtime = { class: "SlowTask", runtime: 2.5, pid: 1234 }
87
+ # ResultInspector.call(result_with_runtime)
88
+ # # => "SlowTask: runtime=2.5 pid=1234"
13
89
  def call(result)
14
90
  ORDERED_KEYS.filter_map do |key|
15
91
  next unless result.key?(key)
@@ -1,16 +1,103 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
+ # Result-specific logging module for task execution outcomes.
5
+ #
6
+ # The ResultLogger module provides specialized logging functionality for
7
+ # CMDx task results. It automatically maps result statuses to appropriate
8
+ # log severity levels and handles conditional logging based on logger
9
+ # availability and configuration.
10
+ #
11
+ # @example Successful task result logging
12
+ # task = ProcessOrderTask.call(order_id: 123)
13
+ # ResultLogger.call(task.result)
14
+ # # Logs at INFO level: "ProcessOrderTask completed successfully"
15
+ #
16
+ # @example Failed task result logging
17
+ # task = ProcessOrderTask.call(invalid_params)
18
+ # ResultLogger.call(task.result)
19
+ # # Logs at ERROR level: "ProcessOrderTask failed with errors"
20
+ #
21
+ # @example Skipped task result logging
22
+ # task = ProcessOrderTask.new
23
+ # task.skip!(reason: "Order already processed")
24
+ # ResultLogger.call(task.result)
25
+ # # Logs at WARN level: "ProcessOrderTask was skipped"
26
+ #
27
+ # @example Integration with task execution
28
+ # class ProcessOrderTask < CMDx::Task
29
+ # def call
30
+ # # Task logic here
31
+ # end
32
+ #
33
+ # # ResultLogger.call is automatically invoked after task execution
34
+ # end
35
+ #
36
+ # @see CMDx::Result Result object status and state management
37
+ # @see CMDx::Logger Logger configuration and setup
38
+ # @see CMDx::Task Task execution and result handling
4
39
  module ResultLogger
5
40
 
41
+ # Mapping of result statuses to corresponding log severity levels.
42
+ #
43
+ # Maps CMDx result status constants to Ruby Logger severity levels
44
+ # to ensure appropriate logging levels for different task outcomes.
6
45
  STATUS_TO_SEVERITY = {
7
- Result::SUCCESS => :info,
8
- Result::SKIPPED => :warn,
9
- Result::FAILED => :error
46
+ Result::SUCCESS => :info, # Successful task completion
47
+ Result::SKIPPED => :warn, # Task was skipped
48
+ Result::FAILED => :error # Task execution failed
10
49
  }.freeze
11
50
 
12
51
  module_function
13
52
 
53
+ # Logs a task result at the appropriate severity level.
54
+ #
55
+ # Determines the appropriate log severity based on the result status
56
+ # and logs the result object using the task's configured logger.
57
+ # Does nothing if no logger is configured for the task.
58
+ #
59
+ # @param result [CMDx::Result] The task result to log
60
+ # @return [void]
61
+ #
62
+ # @example Logging a successful result
63
+ # task = ProcessOrderTask.call(order_id: 123)
64
+ # ResultLogger.call(task.result)
65
+ # # Logs at INFO level with result details
66
+ #
67
+ # @example Logging a failed result
68
+ # task = ProcessOrderTask.new
69
+ # task.fail!(reason: "Invalid order ID")
70
+ # ResultLogger.call(task.result)
71
+ # # Logs at ERROR level with failure details
72
+ #
73
+ # @example Logging a skipped result
74
+ # task = ProcessOrderTask.new
75
+ # task.skip!(reason: "Order already processed")
76
+ # ResultLogger.call(task.result)
77
+ # # Logs at WARN level with skip reason
78
+ #
79
+ # @example No logger configured
80
+ # class SimpleTask < CMDx::Task
81
+ # # No logger setting
82
+ # end
83
+ #
84
+ # task = SimpleTask.call
85
+ # ResultLogger.call(task.result) # Does nothing - no logger available
86
+ #
87
+ # @example Custom logger configuration
88
+ # class MyTask < CMDx::Task
89
+ # task_settings!(
90
+ # logger: Logger.new(STDOUT),
91
+ # log_formatter: CMDx::LogFormatters::Json.new
92
+ # )
93
+ # end
94
+ #
95
+ # task = MyTask.call
96
+ # ResultLogger.call(task.result) # Logs in JSON format to STDOUT
97
+ #
98
+ # @note This method is typically called automatically by the CMDx framework
99
+ # after task execution completes, ensuring that all task results are
100
+ # properly logged according to their outcome.
14
101
  def call(result)
15
102
  logger = result.task.send(:logger)
16
103
  return if logger.nil?
@@ -1,8 +1,84 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
+ # Result serialization utility for converting Result objects to hash representations.
5
+ #
6
+ # The ResultSerializer module provides functionality to serialize Result instances
7
+ # into structured hash representations suitable for inspection, logging, debugging,
8
+ # and data interchange. It handles failure chain information and integrates with
9
+ # TaskSerializer for comprehensive result data.
10
+ #
11
+ # @example Basic result serialization
12
+ # task = ProcessOrderTask.call(order_id: 123)
13
+ # result = task.result
14
+ #
15
+ # ResultSerializer.call(result)
16
+ # # => {
17
+ # # class: "ProcessOrderTask",
18
+ # # type: "Task",
19
+ # # index: 0,
20
+ # # chain_id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
21
+ # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
22
+ # # tags: [],
23
+ # # state: "complete",
24
+ # # status: "success",
25
+ # # outcome: "success",
26
+ # # metadata: {},
27
+ # # runtime: 0.5
28
+ # # }
29
+ #
30
+ # @example Failed result serialization
31
+ # task = ProcessOrderTask.new
32
+ # task.fail!(reason: "Invalid order data", code: 422)
33
+ # result = task.result
34
+ #
35
+ # ResultSerializer.call(result)
36
+ # # => {
37
+ # # class: "ProcessOrderTask",
38
+ # # type: "Task",
39
+ # # index: 0,
40
+ # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
41
+ # # state: "interrupted",
42
+ # # status: "failed",
43
+ # # outcome: "failed",
44
+ # # metadata: { reason: "Invalid order data", code: 422 },
45
+ # # runtime: 0.1,
46
+ # # caused_failure: { ... }, # Failure chain information
47
+ # # threw_failure: { ... }
48
+ # # }
49
+ #
50
+ # @example Result with failure chain
51
+ # # When a result has failure chain information, it's included but
52
+ # # stripped of recursive caused_failure/threw_failure to prevent cycles
53
+ # ResultSerializer.call(result_with_failures)
54
+ # # => {
55
+ # # # ... standard result data ...
56
+ # # caused_failure: {
57
+ # # class: "ValidationTask",
58
+ # # index: 1,
59
+ # # state: "interrupted",
60
+ # # status: "failed"
61
+ # # # caused_failure and threw_failure are stripped to prevent recursion
62
+ # # },
63
+ # # threw_failure: {
64
+ # # class: "ProcessingTask",
65
+ # # index: 2,
66
+ # # state: "interrupted",
67
+ # # status: "failed"
68
+ # # # caused_failure and threw_failure are stripped to prevent recursion
69
+ # # }
70
+ # # }
71
+ #
72
+ # @see CMDx::Result Result object creation and state management
73
+ # @see CMDx::TaskSerializer Task serialization functionality
74
+ # @see CMDx::ResultInspector Human-readable result formatting
4
75
  module ResultSerializer
5
76
 
77
+ # Proc for stripping failure chain information to prevent recursion.
78
+ #
79
+ # This proc is used to include failure chain information (caused_failure
80
+ # and threw_failure) while preventing infinite recursion by stripping
81
+ # the same fields from nested failure objects.
6
82
  STRIP_FAILURE = proc do |h, r, k|
7
83
  unless r.send(:"#{k}?")
8
84
  # Strip caused/threw failures since its the same info as the log line
@@ -12,6 +88,67 @@ module CMDx
12
88
 
13
89
  module_function
14
90
 
91
+ # Converts a Result object to a hash representation.
92
+ #
93
+ # Serializes a Result instance into a structured hash containing all
94
+ # relevant result information including task data, execution state,
95
+ # status, metadata, runtime, and failure chain information.
96
+ #
97
+ # @param result [CMDx::Result] The result object to serialize
98
+ # @return [Hash] Structured hash representation of the result
99
+ #
100
+ # @example Successful result serialization
101
+ # result = ProcessOrderTask.call(order_id: 123).result
102
+ # ResultSerializer.call(result)
103
+ # # => {
104
+ # # class: "ProcessOrderTask",
105
+ # # type: "Task",
106
+ # # index: 0,
107
+ # # chain_id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
108
+ # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
109
+ # # tags: [],
110
+ # # state: "complete",
111
+ # # status: "success",
112
+ # # outcome: "success",
113
+ # # metadata: {},
114
+ # # runtime: 0.5
115
+ # # }
116
+ #
117
+ # @example Failed result with metadata
118
+ # task = ProcessOrderTask.new
119
+ # task.fail!(reason: "Validation failed", errors: ["Invalid email"])
120
+ # result = task.result
121
+ #
122
+ # ResultSerializer.call(result)
123
+ # # => {
124
+ # # class: "ProcessOrderTask",
125
+ # # type: "Task",
126
+ # # index: 0,
127
+ # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
128
+ # # state: "interrupted",
129
+ # # status: "failed",
130
+ # # outcome: "failed",
131
+ # # metadata: { reason: "Validation failed", errors: ["Invalid email"] },
132
+ # # runtime: 0.1
133
+ # # }
134
+ #
135
+ # @example Skipped result serialization
136
+ # task = ProcessOrderTask.new
137
+ # task.skip!(reason: "Order already processed")
138
+ # result = task.result
139
+ #
140
+ # ResultSerializer.call(result)
141
+ # # => {
142
+ # # class: "ProcessOrderTask",
143
+ # # type: "Task",
144
+ # # index: 0,
145
+ # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
146
+ # # state: "interrupted",
147
+ # # status: "skipped",
148
+ # # outcome: "skipped",
149
+ # # metadata: { reason: "Order already processed" },
150
+ # # runtime: 0.05
151
+ # # }
15
152
  def call(result)
16
153
  TaskSerializer.call(result.task).tap do |hash|
17
154
  hash.merge!(