cmdx 1.0.0 → 1.1.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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/rspec.md +20 -0
  3. data/.cursor/prompts/yardoc.md +8 -0
  4. data/.rubocop.yml +5 -0
  5. data/CHANGELOG.md +101 -49
  6. data/README.md +2 -1
  7. data/docs/ai_prompts.md +10 -0
  8. data/docs/basics/call.md +11 -2
  9. data/docs/basics/chain.md +10 -1
  10. data/docs/basics/context.md +9 -0
  11. data/docs/basics/setup.md +9 -0
  12. data/docs/callbacks.md +14 -37
  13. data/docs/configuration.md +68 -27
  14. data/docs/getting_started.md +11 -0
  15. data/docs/internationalization.md +148 -0
  16. data/docs/interruptions/exceptions.md +10 -1
  17. data/docs/interruptions/faults.md +11 -2
  18. data/docs/interruptions/halt.md +9 -0
  19. data/docs/logging.md +14 -4
  20. data/docs/middlewares.md +53 -43
  21. data/docs/outcomes/result.md +9 -0
  22. data/docs/outcomes/states.md +9 -0
  23. data/docs/outcomes/statuses.md +9 -0
  24. data/docs/parameters/coercions.md +58 -38
  25. data/docs/parameters/defaults.md +10 -1
  26. data/docs/parameters/definitions.md +9 -0
  27. data/docs/parameters/namespacing.md +9 -0
  28. data/docs/parameters/validations.md +8 -67
  29. data/docs/testing.md +22 -13
  30. data/docs/tips_and_tricks.md +9 -0
  31. data/docs/workflows.md +14 -4
  32. data/lib/cmdx/.DS_Store +0 -0
  33. data/lib/cmdx/callback.rb +36 -56
  34. data/lib/cmdx/callback_registry.rb +82 -73
  35. data/lib/cmdx/chain.rb +65 -122
  36. data/lib/cmdx/chain_inspector.rb +22 -115
  37. data/lib/cmdx/chain_serializer.rb +17 -148
  38. data/lib/cmdx/coercion.rb +49 -0
  39. data/lib/cmdx/coercion_registry.rb +94 -0
  40. data/lib/cmdx/coercions/array.rb +18 -36
  41. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  42. data/lib/cmdx/coercions/boolean.rb +21 -40
  43. data/lib/cmdx/coercions/complex.rb +18 -31
  44. data/lib/cmdx/coercions/date.rb +20 -39
  45. data/lib/cmdx/coercions/date_time.rb +22 -39
  46. data/lib/cmdx/coercions/float.rb +19 -32
  47. data/lib/cmdx/coercions/hash.rb +22 -41
  48. data/lib/cmdx/coercions/integer.rb +20 -33
  49. data/lib/cmdx/coercions/rational.rb +20 -32
  50. data/lib/cmdx/coercions/string.rb +23 -31
  51. data/lib/cmdx/coercions/time.rb +24 -40
  52. data/lib/cmdx/coercions/virtual.rb +14 -31
  53. data/lib/cmdx/configuration.rb +57 -171
  54. data/lib/cmdx/context.rb +22 -165
  55. data/lib/cmdx/core_ext/hash.rb +42 -67
  56. data/lib/cmdx/core_ext/module.rb +35 -79
  57. data/lib/cmdx/core_ext/object.rb +63 -98
  58. data/lib/cmdx/correlator.rb +40 -156
  59. data/lib/cmdx/error.rb +37 -202
  60. data/lib/cmdx/errors.rb +165 -202
  61. data/lib/cmdx/fault.rb +55 -158
  62. data/lib/cmdx/faults.rb +26 -137
  63. data/lib/cmdx/immutator.rb +22 -109
  64. data/lib/cmdx/lazy_struct.rb +103 -187
  65. data/lib/cmdx/log_formatters/json.rb +14 -40
  66. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  67. data/lib/cmdx/log_formatters/line.rb +14 -48
  68. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  69. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  70. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  71. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  72. data/lib/cmdx/log_formatters/raw.rb +19 -49
  73. data/lib/cmdx/logger.rb +20 -82
  74. data/lib/cmdx/logger_ansi.rb +18 -75
  75. data/lib/cmdx/logger_serializer.rb +24 -114
  76. data/lib/cmdx/middleware.rb +38 -60
  77. data/lib/cmdx/middleware_registry.rb +81 -77
  78. data/lib/cmdx/middlewares/correlate.rb +41 -226
  79. data/lib/cmdx/middlewares/timeout.rb +46 -185
  80. data/lib/cmdx/parameter.rb +120 -198
  81. data/lib/cmdx/parameter_evaluator.rb +231 -0
  82. data/lib/cmdx/parameter_inspector.rb +25 -56
  83. data/lib/cmdx/parameter_registry.rb +59 -84
  84. data/lib/cmdx/parameter_serializer.rb +23 -74
  85. data/lib/cmdx/railtie.rb +24 -107
  86. data/lib/cmdx/result.rb +254 -260
  87. data/lib/cmdx/result_ansi.rb +19 -85
  88. data/lib/cmdx/result_inspector.rb +27 -68
  89. data/lib/cmdx/result_logger.rb +18 -81
  90. data/lib/cmdx/result_serializer.rb +28 -132
  91. data/lib/cmdx/rspec/matchers.rb +28 -0
  92. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  93. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  94. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  95. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  96. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  97. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  98. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  99. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  100. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  101. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  102. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  103. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  104. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  105. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  106. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  107. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  108. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  109. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  110. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  111. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  112. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  113. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  114. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  115. data/lib/cmdx/task.rb +213 -425
  116. data/lib/cmdx/task_deprecator.rb +55 -0
  117. data/lib/cmdx/task_processor.rb +245 -0
  118. data/lib/cmdx/task_serializer.rb +22 -70
  119. data/lib/cmdx/utils/ansi_color.rb +13 -89
  120. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  121. data/lib/cmdx/utils/monotonic_runtime.rb +13 -63
  122. data/lib/cmdx/utils/name_affix.rb +21 -71
  123. data/lib/cmdx/validator.rb +48 -0
  124. data/lib/cmdx/validator_registry.rb +86 -0
  125. data/lib/cmdx/validators/exclusion.rb +55 -94
  126. data/lib/cmdx/validators/format.rb +31 -85
  127. data/lib/cmdx/validators/inclusion.rb +65 -110
  128. data/lib/cmdx/validators/length.rb +117 -133
  129. data/lib/cmdx/validators/numeric.rb +123 -130
  130. data/lib/cmdx/validators/presence.rb +38 -79
  131. data/lib/cmdx/version.rb +1 -7
  132. data/lib/cmdx/workflow.rb +46 -339
  133. data/lib/cmdx.rb +1 -1
  134. data/lib/generators/cmdx/install_generator.rb +14 -31
  135. data/lib/generators/cmdx/task_generator.rb +39 -55
  136. data/lib/generators/cmdx/templates/install.rb +61 -11
  137. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  138. data/lib/locales/ar.yml +35 -0
  139. data/lib/locales/cs.yml +35 -0
  140. data/lib/locales/da.yml +35 -0
  141. data/lib/locales/de.yml +35 -0
  142. data/lib/locales/el.yml +35 -0
  143. data/lib/locales/en.yml +19 -20
  144. data/lib/locales/es.yml +19 -20
  145. data/lib/locales/fi.yml +35 -0
  146. data/lib/locales/fr.yml +35 -0
  147. data/lib/locales/he.yml +35 -0
  148. data/lib/locales/hi.yml +35 -0
  149. data/lib/locales/it.yml +35 -0
  150. data/lib/locales/ja.yml +35 -0
  151. data/lib/locales/ko.yml +35 -0
  152. data/lib/locales/nl.yml +35 -0
  153. data/lib/locales/no.yml +35 -0
  154. data/lib/locales/pl.yml +35 -0
  155. data/lib/locales/pt.yml +35 -0
  156. data/lib/locales/ru.yml +35 -0
  157. data/lib/locales/sv.yml +35 -0
  158. data/lib/locales/th.yml +35 -0
  159. data/lib/locales/tr.yml +35 -0
  160. data/lib/locales/vi.yml +35 -0
  161. data/lib/locales/zh.yml +35 -0
  162. metadata +57 -8
  163. data/lib/cmdx/parameter_validator.rb +0 -81
  164. data/lib/cmdx/parameter_value.rb +0 -244
  165. data/lib/cmdx/parameters_inspector.rb +0 -72
  166. data/lib/cmdx/parameters_serializer.rb +0 -115
  167. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  168. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  169. data/lib/cmdx/validators/custom.rb +0 -102
@@ -1,47 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # ANSI color formatting module for result states and statuses.
4
+ # ANSI color formatting for result states and statuses.
5
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
6
+ # This module provides functionality to apply appropriate ANSI color codes
7
+ # to result states and statuses for enhanced console output readability.
8
+ # It maps execution states and completion statuses to corresponding colors
9
+ # and provides methods to format strings with these colors.
28
10
  module ResultAnsi
29
11
 
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.
34
12
  STATE_COLORS = {
35
13
  Result::INITIALIZED => :blue, # Initial state - blue
36
14
  Result::EXECUTING => :yellow, # Currently executing - yellow
37
15
  Result::COMPLETE => :green, # Successfully completed - green
38
16
  Result::INTERRUPTED => :red # Execution interrupted - red
39
17
  }.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.
45
18
  STATUS_COLORS = {
46
19
  Result::SUCCESS => :green, # Successful completion - green
47
20
  Result::SKIPPED => :yellow, # Intentionally skipped - yellow
@@ -50,71 +23,32 @@ module CMDx
50
23
 
51
24
  module_function
52
25
 
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
26
+ # Applies ANSI color formatting to a string based on its state or status.
61
27
  #
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)
28
+ # @param s [String] the string to format with ANSI color codes
67
29
  #
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)
30
+ # @return [String] the formatted string with appropriate ANSI color codes
72
31
  #
73
- # @example Unknown value
74
- # ResultAnsi.call("unknown") # => "\e[39munknown\e[0m" (default color)
32
+ # @example Format a result state
33
+ # ResultAnsi.call(Result::EXECUTING) #=> "\e[0;33;49mexecuting\e[0m"
75
34
  #
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)
35
+ # @example Format a result status
36
+ # ResultAnsi.call(Result::SUCCESS) #=> "\e[0;32;49msuccess\e[0m"
82
37
  def call(s)
83
38
  Utils::AnsiColor.call(s, color: color(s))
84
39
  end
85
40
 
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
41
+ # Determines the appropriate ANSI color for a given state or status.
101
42
  #
102
- # @example Result status color mapping
103
- # color("success") # => :green
104
- # color("skipped") # => :yellow
105
- # color("failed") # => :red
43
+ # @param s [String] the state or status string to determine color for
106
44
  #
107
- # @example Unknown state or status
108
- # color("unknown") # => :default
109
- # color("pending") # => :default
45
+ # @return [Symbol] the color symbol corresponding to the state/status, or :default if not found
110
46
  #
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
47
+ # @example Get color for a state
48
+ # ResultAnsi.color(Result::COMPLETE) #=> :green
114
49
  #
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
50
+ # @example Get color for unknown value
51
+ # ResultAnsi.color("unknown") #=> :default
118
52
  def color(s)
119
53
  STATE_COLORS[s] || STATUS_COLORS[s] || :default
120
54
  end
@@ -1,53 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Result inspection utility for generating human-readable result descriptions.
4
+ # Provides formatted inspection functionality for task execution results.
5
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
6
+ # This module formats result hash data into a human-readable string representation
7
+ # with ordered key-value pairs. It handles special formatting for specific keys
8
+ # like failure references and applies consistent ordering to result attributes.
45
9
  module ResultInspector
46
10
 
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.
51
11
  ORDERED_KEYS = %i[
52
12
  class type index id state status outcome metadata
53
13
  tags pid runtime caused_failure threw_failure
@@ -55,37 +15,36 @@ module CMDx
55
15
 
56
16
  module_function
57
17
 
58
- # Converts a result hash to a human-readable string representation.
18
+ # Formats a result hash into a human-readable inspection string.
19
+ #
20
+ # Creates a formatted string representation of a result hash with ordered
21
+ # key-value pairs. Special keys like :class, :caused_failure, and :threw_failure
22
+ # receive custom formatting for better readability and debugging.
59
23
  #
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.
24
+ # @param result [Hash] the result hash to format and inspect
63
25
  #
64
- # @param result [Hash] The result hash to inspect
65
- # @return [String] Formatted result description
26
+ # @return [String] formatted string with space-separated key-value pairs
66
27
  #
67
- # @example Simple result inspection
68
- # ResultInspector.call(result_hash)
69
- # # => "ProcessOrderTask: type=Task index=0 id=018c2b95... state=complete status=success"
28
+ # @raise [NoMethodError] if result doesn't respond to key? or []
70
29
  #
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}"
30
+ # @example Formatting a basic result
31
+ # result = { class: "MyTask", state: "complete", status: "success" }
32
+ # ResultInspector.call(result)
33
+ # # => "MyTask: state=complete status=success"
75
34
  #
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" }
35
+ # @example Formatting result with failure information
36
+ # result = {
37
+ # class: "ProcessTask",
38
+ # state: "interrupted",
39
+ # caused_failure: { index: 2, class: "ValidationError", id: "val_123" }
81
40
  # }
82
- # ResultInspector.call(result_with_failures)
83
- # # => "MainTask: caused_failure=<[2] SubTask: abc123> threw_failure=<[1] HelperTask: def456>"
41
+ # ResultInspector.call(result)
42
+ # # => "ProcessTask: state=interrupted caused_failure=<[2] ValidationError: val_123>"
84
43
  #
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"
44
+ # @example Formatting empty or minimal result
45
+ # result = { id: "task_456" }
46
+ # ResultInspector.call(result)
47
+ # # => "id=task_456"
89
48
  def call(result)
90
49
  ORDERED_KEYS.filter_map do |key|
91
50
  next unless result.key?(key)
@@ -1,47 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Result-specific logging module for task execution outcomes.
4
+ # Logger utilities for task execution results.
5
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
6
+ # This module provides functionality to log task execution results with appropriate
7
+ # severity levels based on the result status. It automatically maps result statuses
8
+ # to corresponding log levels and delegates to the task's configured logger.
39
9
  module ResultLogger
40
10
 
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.
45
11
  STATUS_TO_SEVERITY = {
46
12
  Result::SUCCESS => :info, # Successful task completion
47
13
  Result::SKIPPED => :warn, # Task was skipped
@@ -50,54 +16,25 @@ module CMDx
50
16
 
51
17
  module_function
52
18
 
53
- # Logs a task result at the appropriate severity level.
19
+ # Logs the task execution result with appropriate severity level.
54
20
  #
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.
21
+ # This method retrieves the logger from the task and logs the result using
22
+ # the severity level mapped from the result's status. If no logger is configured
23
+ # for the task, the method returns early without logging.
58
24
  #
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
25
+ # @param result [Result] the task execution result to log
78
26
  #
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
27
+ # @return [void]
94
28
  #
95
- # task = MyTask.call
96
- # ResultLogger.call(task.result) # Logs in JSON format to STDOUT
29
+ # @example Log a successful task result
30
+ # result = task.process
31
+ # CMDx::ResultLogger.call(result)
32
+ # # => logs at info level: "Task completed successfully"
97
33
  #
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.
34
+ # @example Log a failed task result
35
+ # result = failing_task.process
36
+ # CMDx::ResultLogger.call(result)
37
+ # # => logs at error level: "Task failed with error"
101
38
  def call(result)
102
39
  logger = result.task.send(:logger)
103
40
  return if logger.nil?
@@ -1,84 +1,14 @@
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
+ # Provides serialization functionality for CMDx::Result objects,
5
+ # converting them into structured hash representations suitable for
6
+ # logging, storage, or transmission.
75
7
  module ResultSerializer
76
8
 
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.
9
+ # A proc that removes failure-related metadata from the hash representation
10
+ # when the result hasn't actually failed in the specified way.
11
+ # This prevents duplicate failure information from appearing in logs.
82
12
  STRIP_FAILURE = proc do |h, r, k|
83
13
  unless r.send(:"#{k}?")
84
14
  # Strip caused/threw failures since its the same info as the log line
@@ -88,67 +18,33 @@ module CMDx
88
18
 
89
19
  module_function
90
20
 
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
21
+ # Converts a Result object into a structured hash representation.
22
+ # Combines task serialization data with result-specific information
23
+ # including execution state, status, outcome, metadata, and runtime.
99
24
  #
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
- # # }
25
+ # @param result [CMDx::Result] the result object to serialize
116
26
  #
117
- # @example Failed result with metadata
118
- # task = ProcessOrderTask.new
119
- # task.fail!(reason: "Validation failed", errors: ["Invalid email"])
120
- # result = task.result
27
+ # @return [Hash] a structured hash containing task information and result details
28
+ # including :state, :status, :outcome, :metadata, :runtime, and optionally
29
+ # :caused_failure and :threw_failure if the result failed
121
30
  #
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
- # # }
31
+ # @raise [NoMethodError] if result doesn't respond to expected methods
32
+ # @raise [TypeError] if result.task is invalid for TaskSerializer
134
33
  #
135
- # @example Skipped result serialization
136
- # task = ProcessOrderTask.new
137
- # task.skip!(reason: "Order already processed")
138
- # result = task.result
34
+ # @example Serializing a successful result
35
+ # result = task.call
36
+ # CMDx::ResultSerializer.call(result)
37
+ # #=> { index: 0, chain_id: "abc123", type: "Task", class: "MyTask",
38
+ # # id: "def456", tags: [], state: "complete", status: "success",
39
+ # # outcome: "good", metadata: {}, runtime: 0.05 }
139
40
  #
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
- # # }
41
+ # @example Serializing a failed result
42
+ # result = task.call
43
+ # CMDx::ResultSerializer.call(result)
44
+ # #=> { index: 0, chain_id: "abc123", type: "Task", class: "MyTask",
45
+ # # id: "def456", tags: [], state: "interrupted", status: "failed",
46
+ # # outcome: "bad", metadata: { error: "Something went wrong" },
47
+ # # runtime: 0.02, caused_failure: {...}, threw_failure: {...} }
152
48
  def call(result)
153
49
  TaskSerializer.call(result.task).tap do |hash|
154
50
  hash.merge!(
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Result matchers
4
+ require_relative "result_matchers/be_successful_task"
5
+ require_relative "result_matchers/be_failed_task"
6
+ require_relative "result_matchers/be_skipped_task"
7
+ require_relative "result_matchers/be_executed"
8
+ require_relative "result_matchers/be_state_matchers"
9
+ require_relative "result_matchers/be_status_matchers"
10
+ require_relative "result_matchers/have_good_outcome"
11
+ require_relative "result_matchers/have_bad_outcome"
12
+ require_relative "result_matchers/have_runtime"
13
+ require_relative "result_matchers/have_metadata"
14
+ require_relative "result_matchers/have_empty_metadata"
15
+ require_relative "result_matchers/have_context"
16
+ require_relative "result_matchers/have_preserved_context"
17
+ require_relative "result_matchers/have_caused_failure"
18
+ require_relative "result_matchers/have_thrown_failure"
19
+ require_relative "result_matchers/have_received_thrown_failure"
20
+ require_relative "result_matchers/have_chain_index"
21
+
22
+ # Task matchers
23
+ require_relative "task_matchers/be_well_formed_task"
24
+ require_relative "task_matchers/have_cmd_setting"
25
+ require_relative "task_matchers/have_middleware"
26
+ require_relative "task_matchers/have_callback"
27
+ require_relative "task_matchers/have_parameter"
28
+ require_relative "task_matchers/have_executed_callbacks"
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher for asserting that a task result has been executed.
4
+ #
5
+ # This matcher checks if a CMDx::Result object is in an executed state,
6
+ # which occurs when the task has finished execution regardless of whether
7
+ # it succeeded, failed, or was skipped. A result is considered executed
8
+ # when it's in either "complete" or "interrupted" state.
9
+ #
10
+ # @return [Boolean] true if the result is executed (complete or interrupted)
11
+ #
12
+ # @example Basic usage with successful task
13
+ # result = MyTask.call(user_id: 123)
14
+ # expect(result).to be_executed
15
+ #
16
+ # @example Usage with failed task
17
+ # result = FailingTask.call
18
+ # expect(result).to be_executed
19
+ #
20
+ # @example Negative assertion
21
+ # task = MyTask.new
22
+ # expect(task.result).not_to be_executed
23
+ #
24
+ # @example In workflow integration tests
25
+ # result = MyWorkflow.call(data: "test")
26
+ # expect(result).to be_executed
27
+ # expect(result.context.processed).to be(true)
28
+ RSpec::Matchers.define :be_executed do
29
+ match(&:executed?)
30
+
31
+ failure_message do |result|
32
+ "expected result to be executed, but was in #{result.state} state"
33
+ end
34
+
35
+ failure_message_when_negated do |result|
36
+ "expected result not to be executed, but it was (state: #{result.state})"
37
+ end
38
+
39
+ description do
40
+ "be executed"
41
+ end
42
+ end