cmdx 0.5.0 → 1.0.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 (151) 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 +19 -1
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +95 -28
  7. data/README.md +73 -25
  8. data/docs/ai_prompts.md +319 -0
  9. data/docs/basics/call.md +234 -14
  10. data/docs/basics/chain.md +280 -0
  11. data/docs/basics/context.md +241 -33
  12. data/docs/basics/setup.md +85 -12
  13. data/docs/callbacks.md +283 -0
  14. data/docs/configuration.md +155 -30
  15. data/docs/getting_started.md +145 -22
  16. data/docs/internationalization.md +148 -0
  17. data/docs/interruptions/exceptions.md +198 -11
  18. data/docs/interruptions/faults.md +196 -44
  19. data/docs/interruptions/halt.md +188 -35
  20. data/docs/logging.md +204 -53
  21. data/docs/middlewares.md +745 -0
  22. data/docs/outcomes/result.md +305 -10
  23. data/docs/outcomes/states.md +212 -31
  24. data/docs/outcomes/statuses.md +284 -30
  25. data/docs/parameters/coercions.md +411 -29
  26. data/docs/parameters/defaults.md +258 -25
  27. data/docs/parameters/definitions.md +247 -72
  28. data/docs/parameters/namespacing.md +259 -27
  29. data/docs/parameters/validations.md +173 -168
  30. data/docs/testing.md +560 -0
  31. data/docs/tips_and_tricks.md +103 -42
  32. data/docs/workflows.md +329 -0
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +69 -0
  35. data/lib/cmdx/callback_registry.rb +106 -0
  36. data/lib/cmdx/chain.rb +190 -0
  37. data/lib/cmdx/chain_inspector.rb +149 -0
  38. data/lib/cmdx/chain_serializer.rb +175 -0
  39. data/lib/cmdx/coercions/array.rb +37 -0
  40. data/lib/cmdx/coercions/big_decimal.rb +33 -0
  41. data/lib/cmdx/coercions/boolean.rb +41 -1
  42. data/lib/cmdx/coercions/complex.rb +31 -0
  43. data/lib/cmdx/coercions/date.rb +39 -0
  44. data/lib/cmdx/coercions/date_time.rb +39 -0
  45. data/lib/cmdx/coercions/float.rb +31 -0
  46. data/lib/cmdx/coercions/hash.rb +42 -0
  47. data/lib/cmdx/coercions/integer.rb +32 -0
  48. data/lib/cmdx/coercions/rational.rb +31 -0
  49. data/lib/cmdx/coercions/string.rb +31 -0
  50. data/lib/cmdx/coercions/time.rb +39 -0
  51. data/lib/cmdx/coercions/virtual.rb +31 -0
  52. data/lib/cmdx/configuration.rb +217 -9
  53. data/lib/cmdx/context.rb +173 -2
  54. data/lib/cmdx/core_ext/hash.rb +72 -0
  55. data/lib/cmdx/core_ext/module.rb +94 -0
  56. data/lib/cmdx/core_ext/object.rb +105 -0
  57. data/lib/cmdx/correlator.rb +217 -0
  58. data/lib/cmdx/error.rb +210 -8
  59. data/lib/cmdx/errors.rb +256 -1
  60. data/lib/cmdx/fault.rb +177 -2
  61. data/lib/cmdx/faults.rb +158 -2
  62. data/lib/cmdx/immutator.rb +121 -2
  63. data/lib/cmdx/lazy_struct.rb +261 -18
  64. data/lib/cmdx/log_formatters/json.rb +46 -0
  65. data/lib/cmdx/log_formatters/key_value.rb +46 -0
  66. data/lib/cmdx/log_formatters/line.rb +54 -0
  67. data/lib/cmdx/log_formatters/logstash.rb +64 -0
  68. data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
  69. data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
  70. data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
  71. data/lib/cmdx/log_formatters/raw.rb +54 -0
  72. data/lib/cmdx/logger.rb +85 -0
  73. data/lib/cmdx/logger_ansi.rb +93 -7
  74. data/lib/cmdx/logger_serializer.rb +116 -0
  75. data/lib/cmdx/middleware.rb +74 -0
  76. data/lib/cmdx/middleware_registry.rb +106 -0
  77. data/lib/cmdx/middlewares/correlate.rb +266 -0
  78. data/lib/cmdx/middlewares/timeout.rb +232 -0
  79. data/lib/cmdx/parameter.rb +228 -1
  80. data/lib/cmdx/parameter_inspector.rb +61 -0
  81. data/lib/cmdx/parameter_registry.rb +125 -0
  82. data/lib/cmdx/parameter_serializer.rb +83 -0
  83. data/lib/cmdx/parameter_validator.rb +62 -0
  84. data/lib/cmdx/parameter_value.rb +109 -1
  85. data/lib/cmdx/parameters_inspector.rb +59 -0
  86. data/lib/cmdx/parameters_serializer.rb +102 -0
  87. data/lib/cmdx/railtie.rb +123 -3
  88. data/lib/cmdx/result.rb +367 -25
  89. data/lib/cmdx/result_ansi.rb +105 -9
  90. data/lib/cmdx/result_inspector.rb +76 -0
  91. data/lib/cmdx/result_logger.rb +90 -3
  92. data/lib/cmdx/result_serializer.rb +137 -0
  93. data/lib/cmdx/rspec/result_matchers.rb +917 -0
  94. data/lib/cmdx/rspec/task_matchers.rb +570 -0
  95. data/lib/cmdx/task.rb +405 -37
  96. data/lib/cmdx/task_serializer.rb +74 -2
  97. data/lib/cmdx/utils/ansi_color.rb +95 -0
  98. data/lib/cmdx/utils/log_timestamp.rb +48 -0
  99. data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
  100. data/lib/cmdx/utils/name_affix.rb +78 -0
  101. data/lib/cmdx/validators/custom.rb +82 -0
  102. data/lib/cmdx/validators/exclusion.rb +94 -0
  103. data/lib/cmdx/validators/format.rb +102 -8
  104. data/lib/cmdx/validators/inclusion.rb +104 -0
  105. data/lib/cmdx/validators/length.rb +128 -0
  106. data/lib/cmdx/validators/numeric.rb +128 -0
  107. data/lib/cmdx/validators/presence.rb +93 -7
  108. data/lib/cmdx/version.rb +7 -1
  109. data/lib/cmdx/workflow.rb +394 -0
  110. data/lib/cmdx.rb +25 -64
  111. data/lib/generators/cmdx/install_generator.rb +37 -1
  112. data/lib/generators/cmdx/task_generator.rb +69 -1
  113. data/lib/generators/cmdx/templates/install.rb +43 -15
  114. data/lib/generators/cmdx/workflow_generator.rb +109 -0
  115. data/lib/locales/ar.yml +36 -0
  116. data/lib/locales/cs.yml +36 -0
  117. data/lib/locales/da.yml +36 -0
  118. data/lib/locales/de.yml +36 -0
  119. data/lib/locales/el.yml +36 -0
  120. data/lib/locales/en.yml +20 -20
  121. data/lib/locales/es.yml +20 -20
  122. data/lib/locales/fi.yml +36 -0
  123. data/lib/locales/fr.yml +36 -0
  124. data/lib/locales/he.yml +36 -0
  125. data/lib/locales/hi.yml +36 -0
  126. data/lib/locales/it.yml +36 -0
  127. data/lib/locales/ja.yml +36 -0
  128. data/lib/locales/ko.yml +36 -0
  129. data/lib/locales/nl.yml +36 -0
  130. data/lib/locales/no.yml +36 -0
  131. data/lib/locales/pl.yml +36 -0
  132. data/lib/locales/pt.yml +36 -0
  133. data/lib/locales/ru.yml +36 -0
  134. data/lib/locales/sv.yml +36 -0
  135. data/lib/locales/th.yml +36 -0
  136. data/lib/locales/tr.yml +36 -0
  137. data/lib/locales/vi.yml +36 -0
  138. data/lib/locales/zh.yml +36 -0
  139. metadata +77 -15
  140. data/docs/basics/run.md +0 -34
  141. data/docs/batch.md +0 -53
  142. data/docs/example.md +0 -82
  143. data/docs/hooks.md +0 -62
  144. data/lib/cmdx/batch.rb +0 -43
  145. data/lib/cmdx/parameters.rb +0 -35
  146. data/lib/cmdx/run.rb +0 -39
  147. data/lib/cmdx/run_inspector.rb +0 -26
  148. data/lib/cmdx/run_serializer.rb +0 -20
  149. data/lib/cmdx/task_hook.rb +0 -18
  150. data/lib/generators/cmdx/batch_generator.rb +0 -30
  151. /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
@@ -1,57 +1,182 @@
1
1
  # Configuration
2
2
 
3
- Configuration of tasks can be done at global and task levels.
3
+ CMDx provides a flexible configuration system that allows customization at both global and task levels. Configuration follows a hierarchy where global settings serve as defaults that can be overridden at the task level.
4
4
 
5
- ## Global settings
5
+ ## Table of Contents
6
6
 
7
- Run `rails g cmdx:install` to generate a configuration file within the
8
- `config/initializers` directory. These settings will be preloaded into all tasks.
7
+ - [TLDR](#tldr)
8
+ - [Configuration Hierarchy](#configuration-hierarchy)
9
+ - [Global Configuration](#global-configuration)
10
+ - [Configuration Options](#configuration-options)
11
+ - [Global Middlewares](#global-middlewares)
12
+ - [Global Callbacks](#global-callbacks)
13
+ - [Task Settings](#task-settings)
14
+ - [Available Task Settings](#available-task-settings)
15
+ - [Workflow Configuration](#workflow-configuration)
16
+ - [Configuration Management](#configuration-management)
17
+ - [Accessing Configuration](#accessing-configuration)
18
+ - [Resetting Configuration](#resetting-configuration)
19
+
20
+ ## TLDR
21
+
22
+ - **Hierarchy** - Global → Task Settings → Runtime (each level overrides previous)
23
+ - **Global config** - Framework-wide defaults via `CMDx.configure`
24
+ - **Task settings** - Class-level overrides using `task_settings!`
25
+ - **Key options** - `task_halt`, `workflow_halt`, `logger`, `middlewares`, `callbacks`
26
+ - **Generator** - Use `rails g cmdx:install` to create configuration file
27
+ - **Inheritance** - Settings are inherited from parent classes
28
+
29
+ ## Configuration Hierarchy
30
+
31
+ CMDx follows a three-tier configuration hierarchy:
32
+
33
+ 1. **Global Configuration**: Framework-wide defaults
34
+ 2. **Task Settings**: Class-level overrides via `task_settings!`
35
+ 3. **Runtime Parameters**: Instance-specific overrides during execution
36
+
37
+ > [!IMPORTANT]
38
+ > Task-level settings take precedence over global configuration. Settings are inherited from superclasses and can be overridden in subclasses.
39
+
40
+ ## Global Configuration
41
+
42
+ Generate a configuration file using the Rails generator:
43
+
44
+ ```bash
45
+ rails g cmdx:install
46
+ ```
47
+
48
+ This creates `config/initializers/cmdx.rb` with default settings.
49
+
50
+ ### Configuration Options
51
+
52
+ | Option | Type | Default | Description |
53
+ |---------------|-----------------------|----------------|-------------|
54
+ | `task_halt` | String, Array<String> | `"failed"` | Result statuses that cause `call!` to raise faults |
55
+ | `workflow_halt` | String, Array<String> | `"failed"` | Result statuses that halt workflow execution |
56
+ | `logger` | Logger | Line formatter | Logger instance for task execution logging |
57
+ | `middlewares` | MiddlewareRegistry | Empty registry | Global middleware registry applied to all tasks |
58
+ | `callbacks` | CallbackRegistry | Empty registry | Global callback registry applied to all tasks |
59
+
60
+ ### Global Middlewares
61
+
62
+ Configure middlewares that automatically apply to all tasks in your application:
9
63
 
10
64
  ```ruby
11
65
  CMDx.configure do |config|
12
- # Define which statuses a bang `call!` will halt and raise a fault.
13
- # This option can accept an array of statuses.
14
- config.task_halt = CMDx::Result::FAILED
66
+ # Add middlewares without arguments
67
+ config.middlewares.use CMDx::Middlewares::Timeout
15
68
 
16
- # Enable task timeouts to prevent call execution beyond a defined threshold.
17
- config.task_timeout = nil
69
+ # Add middlewares with arguments
70
+ config.middlewares.use CMDx::Middlewares::Timeout, seconds: 30
18
71
 
19
- # Define which statuses a batch task will halt execution from proceeding to the next step.
20
- # By default skipped tasks are treated as a NOOP so processing is continued.
21
- # This option can accept an array of statuses.
22
- config.batch_halt = CMDx::Result::FAILED
72
+ # Add middleware instances
73
+ config.middlewares.use CMDx::Middlewares::Timeout.new(seconds: 30)
74
+ end
75
+ ```
23
76
 
24
- # Enable batch timeouts to prevent call execution beyond a defined threshold.
25
- # TIP: remember to account for all defined tasks when setting this value
26
- config.batch_timeout = nil
77
+ ### Global Callbacks
27
78
 
28
- # A list of available log formatter can be found at:
29
- # https://github.com/drexed/cmdx/tree/main/lib/cmdx/log_formatters
30
- config.logger = Logger.new($stdout, formatter: CMDx::LogFormatters::Line.new)
79
+ Configure callbacks that automatically apply to all tasks in your application:
80
+
81
+ ```ruby
82
+ CMDx.configure do |config|
83
+ # Add method callbacks
84
+ config.callbacks.register :before_execution, :log_task_start
85
+ config.callbacks.register :after_execution, :log_task_end
86
+
87
+ # Add callback instances
88
+ config.callbacks.register :on_success, NotificationCallback.new([:slack])
89
+ config.callbacks.register :on_failure, AlertCallback.new(severity: :critical)
90
+
91
+ # Add conditional callbacks
92
+ config.callbacks.register :on_failure, :page_admin, if: :production?
93
+ config.callbacks.register :before_validation, :skip_validation, unless: :validate_params?
94
+
95
+ # Add proc callbacks
96
+ config.callbacks.register :on_complete, proc { |task, callback_type|
97
+ Metrics.increment("task.#{task.class.name.underscore}.completed")
98
+ }
31
99
  end
32
100
  ```
33
101
 
34
- ## Task settings
102
+ ## Task Settings
35
103
 
36
- Finely tune tasks via class level settings.
104
+ Override global configuration for specific tasks or workflows using `task_settings!`:
37
105
 
38
106
  ```ruby
39
- class ProcessOrderTask < CMDx::Task
40
-
41
- # Task level settings:
42
- task_settings!(task_timeout: 1, tags: ["v1", "DEPRECATED"])
107
+ class ProcessPaymentTask < CMDx::Task
108
+ task_settings!(
109
+ task_halt: ["failed"], # Only halt on failures
110
+ tags: ["payments", "critical"], # Add logging tags
111
+ logger: Rails.logger, # Use Rails logger
112
+ log_level: :info, # Set log level
113
+ log_formatter: CMDx::LogFormatters::Json.new # JSON formatter
114
+ )
43
115
 
44
116
  def call
45
- # Do work...
117
+ # Process payment logic
46
118
  end
119
+ end
120
+ ```
121
+
122
+ ### Available Task Settings
123
+
124
+ | Setting | Type | Description |
125
+ |-----------------|-----------------------|-------------|
126
+ | `task_halt` | String, Array<String> | Result statuses that cause `call!` to raise faults |
127
+ | `workflow_halt` | String, Array<String> | Result statuses that halt workflow execution |
128
+ | `tags` | Array<String> | Tags automatically appended to logs |
129
+ | `logger` | Logger | Custom logger instance |
130
+ | `log_level` | Symbol | Log level (`:debug`, `:info`, `:warn`, `:error`, `:fatal`) |
131
+ | `log_formatter` | LogFormatter | Custom log formatter |
47
132
 
133
+ ### Workflow Configuration
134
+
135
+ Configure halt behavior for workflows:
136
+
137
+ ```ruby
138
+ class OrderProcessingWorkflow < CMDx::Workflow
139
+ # Strict workflow - halt on any failure
140
+ task_settings!(workflow_halt: ["failed", "skipped"])
141
+
142
+ process ValidateOrderTask
143
+ process ChargePaymentTask
144
+ process FulfillOrderTask
48
145
  end
49
146
  ```
50
147
 
51
- > [!NOTE]
52
- > `tags` is a task level setting only. Tags will automatically be appended to logs.
148
+ ## Configuration Management
149
+
150
+ ### Accessing Configuration
151
+
152
+ ```ruby
153
+ # Global configuration
154
+ CMDx.configuration.logger #=> <Logger instance>
155
+ CMDx.configuration.task_halt #=> "failed"
156
+ CMDx.configuration.middlewares #=> <MiddlewareRegistry instance>
157
+ CMDx.configuration.callbacks #=> <CallbackRegistry instance>
158
+
159
+ # Task-specific settings
160
+ class AnalyzeDataTask < CMDx::Task
161
+ task_settings!(tags: ["analytics"])
162
+
163
+ def call
164
+ tags = task_setting(:tags) # Gets ["analytics"]
165
+ halt_statuses = task_setting(:task_halt) # Gets global default
166
+ end
167
+ end
168
+ ```
169
+
170
+ ### Resetting Configuration
171
+
172
+ Reset configuration to defaults (useful for testing):
173
+
174
+ ```ruby
175
+ CMDx.reset_configuration!
176
+ CMDx.configuration.task_halt #=> "failed" (default)
177
+ ```
53
178
 
54
179
  ---
55
180
 
56
- - **Prev:** [Configuration](https://github.com/drexed/cmdx/blob/main/docs/configuration.md)
57
- - **Next:** [Basics - Setup](https://github.com/drexed/cmdx/blob/main/docs/basics/setup.md)
181
+ - **Prev:** [Getting Started](getting_started.md)
182
+ - **Next:** [Basics - Setup](basics/setup.md)
@@ -1,47 +1,170 @@
1
- # Getting Start
1
+ # Getting Started
2
2
 
3
- `CMDx` is a framework for expressive processing of business logic.
3
+ CMDx is a Ruby framework for building maintainable, observable business logic through composable
4
+ command objects. Design robust workflows with automatic parameter validation, structured error
5
+ handling, comprehensive logging, and intelligent execution flow control that scales from simple
6
+ tasks to complex multi-step processes.
4
7
 
5
- Goals:
6
- - Provide easy branching, nesting, and composition of complex tasks
7
- - Supply intent, severity, and reasoning to halting execution of tasks
8
- - Demystify root causes of complex multi-level tasks with exhaustive tracing
8
+ ## Table of Contents
9
9
 
10
- ## Setup
10
+ - [TLDR](#tldr)
11
+ - [Installation](#installation)
12
+ - [Quick Setup](#quick-setup)
13
+ - [Execution](#execution)
14
+ - [Result Handling](#result-handling)
15
+ - [Exception Handling](#exception-handling)
16
+ - [Building Workflows](#building-workflows)
17
+ - [Code Generation](#code-generation)
18
+
19
+ ## TLDR
20
+
21
+ - **Installation** - Add `gem 'cmdx'` to Gemfile, run `rails g cmdx:install`
22
+ - **Tasks** - Ruby classes inheriting from `CMDx::Task` with `call` method
23
+ - **Execution** - Use `call` (returns result) or `call!` (raises on failure/skip)
24
+ - **Parameters** - Define with `required`/`optional` with type coercion and validation
25
+ - **Results** - Check `result.status` for success/skipped/failed outcomes
26
+ - **Workflows** - Orchestrate multiple tasks with `CMDx::Workflow`
27
+ - **Generators** - Use `rails g cmdx:task` and `rails g cmdx:workflow` for scaffolding
28
+
29
+ ## Installation
30
+
31
+ Add CMDx to your Gemfile:
32
+
33
+ ```ruby
34
+ gem 'cmdx'
35
+ ```
36
+
37
+ For Rails applications, generate the configuration:
38
+
39
+ ```bash
40
+ rails generate cmdx:install
41
+ ```
42
+
43
+ > [!NOTE]
44
+ > This creates `config/initializers/cmdx.rb` with default settings.
45
+
46
+ ## Quick Setup
11
47
 
12
48
  ```ruby
13
49
  class ProcessOrderTask < CMDx::Task
50
+ required :order_id, type: :integer
51
+ optional :send_email, type: :boolean, default: true
14
52
 
15
53
  def call
16
- fail!(reason: "Order was canceled") if context.order.canceled?
17
- skip!(reason: "Order is processing") if context.order.processing?
54
+ context.order = Order.find(order_id)
18
55
 
19
- inform_partner_warehouses
20
- send_confirmation_email
56
+ if context.order.canceled?
57
+ fail!(reason: "Order canceled", canceled_at: context.order.canceled_at)
58
+ elsif context.order.completed?
59
+ skip!(reason: "Already processed")
60
+ else
61
+ context.order.update!(status: 'completed', completed_at: Time.now)
62
+ EmailService.send_confirmation(context.order) if send_email
63
+ end
21
64
  end
22
-
23
65
  end
24
66
  ```
25
67
 
68
+ > [!TIP]
69
+ > Use **present tense verbs** for clarity: `ProcessOrderTask`, `SendEmailTask`, `ValidatePaymentTask`
70
+
26
71
  ## Execution
27
72
 
73
+ Execute tasks using class methods:
74
+
75
+ ```ruby
76
+ # Returns Result object
77
+ result = ProcessOrderTask.call(order_id: 123)
78
+
79
+ # Raises exceptions on failure/skip, else returns Result object
80
+ result = ProcessOrderTask.call!(order_id: 123, send_email: false)
81
+ ```
82
+
83
+ ## Result Handling
84
+
85
+ Check execution outcomes:
86
+
87
+ ```ruby
88
+ result = ProcessOrderTask.call(order_id: 123)
89
+
90
+ case result.status
91
+ when 'success'
92
+ redirect_to order_path(result.context.order), notice: "Order processed!"
93
+ when 'skipped'
94
+ redirect_to order_path(result.context.order), notice: result.metadata[:reason]
95
+ when 'failed'
96
+ redirect_to orders_path, alert: "Error: #{result.metadata[:reason]}"
97
+ end
98
+
99
+ # Access execution metadata
100
+ puts "Runtime: #{result.runtime}s, Task ID: #{result.id}"
101
+ ```
102
+
103
+ ## Exception Handling
104
+
105
+ Use `call!` for exception-based control flow:
106
+
28
107
  ```ruby
29
- result = ProcessOrderTask.call(order: order)
108
+ begin
109
+ result = ProcessOrderTask.call!(order_id: 123)
110
+ redirect_to order_path(result.context.order), notice: "Success!"
111
+ rescue CMDx::Skipped => e
112
+ redirect_to orders_path, notice: e.result.metadata[:reason]
113
+ rescue CMDx::Failed => e
114
+ redirect_to order_path(123), alert: e.result.metadata[:reason]
115
+ end
30
116
  ```
31
117
 
32
- ## Result
118
+ ## Building Workflows
119
+
120
+ Combine tasks using workflows:
33
121
 
34
122
  ```ruby
35
- if result.failed?
36
- flash[:error] = "Failed! #{result.metadata[:reason]}"
37
- elsif result.skipped?
38
- flash[:notice] = "FYI: #{result.metadata[:reason]}"
39
- else
40
- flash[:success] = "Order successfully processed"
123
+ class OrderProcessingWorkflow < CMDx::Workflow
124
+ required :order_id, type: :integer
125
+
126
+ before_execution :log_workflow_start
127
+ on_failed :notify_support
128
+
129
+ process ValidateOrderTask
130
+ process ChargePaymentTask
131
+ process UpdateInventoryTask
132
+ process SendConfirmationTask, if: proc { context.payment_successful? }
133
+
134
+ # NO call method
135
+
136
+ private
137
+
138
+ def log_workflow_start
139
+ Rails.logger.info "Starting order workflow for order #{order_id}"
140
+ end
141
+
142
+ def notify_support
143
+ SupportNotifier.alert("Order workflow failed", context: context.to_h)
144
+ end
41
145
  end
146
+
147
+ result = ProcessOrderWorkflow.call(order_id: 123)
42
148
  ```
43
149
 
150
+ ## Code Generation
151
+
152
+ Generate tasks and workflows with proper structure:
153
+
154
+ ```bash
155
+ # Generate individual task
156
+ rails generate cmdx:task ProcessOrder
157
+ # Creates: app/cmds/process_order_task.rb
158
+
159
+ # Generate task workflow
160
+ rails generate cmdx:workflow OrderDeliveryWorkflow
161
+ # Creates: app/cmds/order_delivery_workflow.rb
162
+ ```
163
+
164
+ > [!NOTE]
165
+ > Generators automatically handle naming conventions and inherit from `ApplicationTask`/`ApplicationWorkflow` when available.
166
+
44
167
  ---
45
168
 
46
- - **Prev:** [Example](https://github.com/drexed/cmdx/blob/main/docs/example.md)
47
- - **Next:** [Configuration](https://github.com/drexed/cmdx/blob/main/docs/configuration.md)
169
+ - **Prev:** [Tips and Tricks](tips_and_tricks.md)
170
+ - **Next:** [Configuration](configuration.md)
@@ -0,0 +1,148 @@
1
+ # Internationalization (i18n)
2
+
3
+ CMDx provides comprehensive internationalization support for all error messages, including parameter coercion errors, validation failures, and fault messages. All error text is automatically localized based on the current `I18n.locale`.
4
+
5
+ ## Table of Contents
6
+
7
+ - [TLDR](#tldr)
8
+ - [Available Locales](#available-locales)
9
+ - [Fault Messages](#fault-messages)
10
+ - [Parameter Messages](#parameter-messages)
11
+ - [Coercion Messages](#coercion-messages)
12
+ - [Validation Messages](#validation-messages)
13
+
14
+ ## TLDR
15
+
16
+ - **24 languages** - Built-in translations for major world languages
17
+ - **Automatic localization** - Based on `I18n.locale` setting
18
+ - **Complete coverage** - Coercion errors, validation failures, and fault messages
19
+ - **Custom overrides** - Parameter-specific messages override locale defaults
20
+
21
+ ## Available Locales
22
+
23
+ CMDx includes built-in translations for 24 languages:
24
+
25
+ | Language | Locale | Language | Locale |
26
+ |----------|--------|----------|--------|
27
+ | English | `:en` | Russian | `:ru` |
28
+ | Spanish | `:es` | Arabic | `:ar` |
29
+ | French | `:fr` | Korean | `:ko` |
30
+ | German | `:de` | Dutch | `:nl` |
31
+ | Portuguese | `:pt` | Swedish | `:sv` |
32
+ | Italian | `:it` | Hindi | `:hi` |
33
+ | Japanese | `:ja` | Polish | `:pl` |
34
+ | Chinese | `:zh` | Turkish | `:tr` |
35
+ | Norwegian | `:no` | Danish | `:da` |
36
+ | Finnish | `:fi` | Greek | `:el` |
37
+ | Hebrew | `:he` | Thai | `:th` |
38
+ | Vietnamese | `:vi` | Czech | `:cs` |
39
+
40
+ ## Fault Messages
41
+
42
+ Default fault messages from `skip!` and `fail!` methods are localized:
43
+
44
+ ```ruby
45
+ class ProcessPaymentTask < CMDx::Task
46
+ def call
47
+ # When no reason is provided, uses localized default
48
+ fail! if payment_declined?
49
+ end
50
+ end
51
+
52
+ # English
53
+ I18n.locale = :en
54
+ result = ProcessPaymentTask.call(payment_id: 123)
55
+ result.metadata[:reason] #=> "no reason given"
56
+
57
+ # Chinese
58
+ I18n.locale = :zh
59
+ result = ProcessPaymentTask.call(payment_id: 123)
60
+ result.metadata[:reason] #=> "未提供原因"
61
+ ```
62
+
63
+ ## Parameter Messages
64
+
65
+ Parameter required or undefined source errors are automatically localized:
66
+
67
+ ```ruby
68
+ class ProcessOrderTask < CMDx::Task
69
+ required :order_id, type: :integer
70
+ optional :user_name, source: :nonexistent_method
71
+
72
+ def call
73
+ # Task implementation
74
+ end
75
+ end
76
+
77
+ # English locale
78
+ I18n.locale = :en
79
+ result = ProcessOrderTask.call({}) # Missing required parameter
80
+ result.metadata[:messages][:order_id] #=> ["is a required parameter"]
81
+
82
+ result = ProcessOrderTask.call(order_id: 123) # Undefined source method
83
+ result.metadata[:messages][:user_name] #=> ["delegates to undefined method nonexistent_method"]
84
+
85
+ # Spanish locale
86
+ I18n.locale = :es
87
+ result = ProcessOrderTask.call({}) # Missing required parameter
88
+ result.metadata[:messages][:order_id] #=> ["es un parámetro requerido"]
89
+
90
+ result = ProcessOrderTask.call(order_id: 123) # Undefined source method
91
+ result.metadata[:messages][:user_name] #=> ["delegado al método indefinido nonexistent_method"]
92
+ ```
93
+
94
+ ## Coercion Messages
95
+
96
+ Type conversion errors are automatically localized:
97
+
98
+ ```ruby
99
+ class ProcessOrderTask < CMDx::Task
100
+ required :order_id, type: :integer
101
+ required :amount, type: :float
102
+
103
+ def call
104
+ # Task implementation
105
+ end
106
+ end
107
+
108
+ # English
109
+ I18n.locale = :en
110
+ result = ProcessOrderTask.call(order_id: "invalid", amount: "bad")
111
+ result.metadata[:messages][:order_id] #=> ["could not coerce into an integer"]
112
+
113
+ # Spanish
114
+ I18n.locale = :es
115
+ result = ProcessOrderTask.call(order_id: "invalid", amount: "bad")
116
+ result.metadata[:messages][:order_id] #=> ["no podía coacciona el valor a un integer"]
117
+ ```
118
+
119
+ ## Validation Messages
120
+
121
+ All validator error messages support internationalization:
122
+
123
+ ```ruby
124
+ class RegisterUserTask < CMDx::Task
125
+ required :email, format: { with: /@/ }
126
+ required :age, numeric: { min: 18 }
127
+ required :status, inclusion: { in: %w[active inactive] }
128
+
129
+ def call
130
+ # Task implementation
131
+ end
132
+ end
133
+
134
+ # English
135
+ I18n.locale = :en
136
+ result = RegisterUserTask.call(email: "invalid", age: 16, status: "unknown")
137
+ result.metadata[:messages][:email] #=> ["is an invalid format"]
138
+
139
+ # Japanese
140
+ I18n.locale = :ja
141
+ result = RegisterUserTask.call(email: "invalid", age: 16, status: "unknown")
142
+ result.metadata[:messages][:email] #=> ["無効な形式です"]
143
+ ```
144
+
145
+ ---
146
+
147
+ - **Prev:** [Logging](logging.md)
148
+ - **Next:** [Testing](testing.md)