cmdx 2.1.1 → 2.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -1
- data/lib/cmdx/callbacks.rb +38 -7
- data/lib/cmdx/coercions.rb +1 -0
- data/lib/cmdx/fault.rb +38 -0
- data/lib/cmdx/input.rb +12 -2
- data/lib/cmdx/inputs.rb +4 -0
- data/lib/cmdx/runtime.rb +2 -1
- data/lib/cmdx/task.rb +3 -0
- data/lib/cmdx/version.rb +1 -1
- data/lib/generators/cmdx/templates/install.rb +4 -3
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f8adbc69a3e91974b801b1f11c404a12b12bdcfcfd3f3d947a8cb3c730b7a934
|
|
4
|
+
data.tar.gz: e0daecd4316350294630dc58f1a05bf1c0fde6203360981b156e468504108a29
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 93022dbeef63aab15d43cc0902d97dc0e0e41aa72edfc2ecc00d57b7d103e0a2518271b4fa5f0cd0f4e037d39d9bda5a2c07210c24a76996bfbc830785f521fb
|
|
7
|
+
data.tar.gz: 57a130066332c5bc60645c3dff28eb5909711c18eeb54d304961ba4787543d270773e52161f5a95dca988a971972d80de18547c1f2d59a5e6cd7f39342f28c69
|
data/CHANGELOG.md
CHANGED
|
@@ -4,12 +4,21 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## UNRELEASED
|
|
8
|
+
|
|
9
|
+
## [2.2.0] - UNRELEASED
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Post-work lifecycle callbacks (`after_execution`, every `on_*` hook) receive the finalized `Signal` as their second argument — `reason`, `cause`, `metadata`, `origin`, and `backtrace` are available before `Result` construction. Pre-work hooks and `around_execution` keep the single-`task` arity
|
|
13
|
+
- `Input#allow_nil` — permit `nil` after defaults without coercion, transform, or validation
|
|
14
|
+
- `Fault#detailed_message` — appends validation errors and propagation `origin` to the stdlib detailed message for IRB and debug formatters without changing `message`
|
|
15
|
+
- `Fault#inspect` — compact task/reason/errors summary for console debugging
|
|
8
16
|
|
|
9
17
|
## [2.1.1] - 2026-07-26
|
|
10
18
|
|
|
11
19
|
### Added
|
|
12
20
|
- `Result#to_h` includes `:errors` (`Errors#to_h`) for structured validation messages in logs, telemetry, and JSON serialization
|
|
21
|
+
- Add `:decimal` coercion alias for `BigDecimal`
|
|
13
22
|
|
|
14
23
|
### Fixed
|
|
15
24
|
- `Input` resolution continues coerce → transform → validate when the key is present (including explicit `nil`) instead of stopping after defaults; omitted keys still stop when the value is `nil` after defaults
|
data/lib/cmdx/callbacks.rb
CHANGED
|
@@ -25,6 +25,20 @@ module CMDx
|
|
|
25
25
|
:on_ko
|
|
26
26
|
].freeze
|
|
27
27
|
|
|
28
|
+
# Events fired after `around_execution` (work, rollback) finishes. Runtime
|
|
29
|
+
# passes the finalized {Signal} as the second callback argument so hooks can
|
|
30
|
+
# read `reason`, `cause`, `metadata`, `origin`, and `backtrace`.
|
|
31
|
+
POST_WORK_EVENTS = Set[
|
|
32
|
+
:after_execution,
|
|
33
|
+
:on_complete,
|
|
34
|
+
:on_interrupted,
|
|
35
|
+
:on_success,
|
|
36
|
+
:on_skipped,
|
|
37
|
+
:on_failed,
|
|
38
|
+
:on_ok,
|
|
39
|
+
:on_ko
|
|
40
|
+
].freeze
|
|
41
|
+
|
|
28
42
|
attr_reader :registry
|
|
29
43
|
|
|
30
44
|
def initialize
|
|
@@ -122,20 +136,26 @@ module CMDx
|
|
|
122
136
|
# Fires each callback registered for `event` against `task`. Skips any
|
|
123
137
|
# callback whose `:if`/`:unless` gates fail.
|
|
124
138
|
#
|
|
139
|
+
# Post-work events ({POST_WORK_EVENTS}) receive the finalized `signal` as
|
|
140
|
+
# their second argument (Symbol method, Proc, or `#call`). Pre-work events
|
|
141
|
+
# and `around_execution` omit it.
|
|
142
|
+
#
|
|
125
143
|
# @param event [Symbol]
|
|
126
144
|
# @param task [Task]
|
|
145
|
+
# @param signal [Signal, nil] finalized halt token for post-work events
|
|
127
146
|
# @return [void]
|
|
128
147
|
# @raise [ArgumentError] when a callback is neither a Symbol nor responds to `#call`
|
|
129
|
-
def process(event, task)
|
|
148
|
+
def process(event, task, signal = nil)
|
|
130
149
|
return if empty?
|
|
131
150
|
|
|
132
151
|
callbacks = registry[event]
|
|
133
152
|
return if callbacks.nil? || callbacks.empty?
|
|
134
153
|
|
|
154
|
+
post_work = POST_WORK_EVENTS.include?(event)
|
|
135
155
|
callbacks.each do |callable, options|
|
|
136
156
|
next unless Util.satisfied?(options[:if], options[:unless], task)
|
|
137
157
|
|
|
138
|
-
invoke(callable, task)
|
|
158
|
+
invoke(callable, task, signal: post_work ? signal : nil)
|
|
139
159
|
end
|
|
140
160
|
end
|
|
141
161
|
|
|
@@ -167,7 +187,7 @@ module CMDx
|
|
|
167
187
|
succ.call
|
|
168
188
|
end
|
|
169
189
|
|
|
170
|
-
invoke(callable, task, cont, &cont)
|
|
190
|
+
invoke(callable, task, continuation: cont, &cont)
|
|
171
191
|
|
|
172
192
|
called || raise(CallbackError, <<~MSG.chomp)
|
|
173
193
|
#{event} callback did not invoke its continuation.
|
|
@@ -179,14 +199,25 @@ module CMDx
|
|
|
179
199
|
|
|
180
200
|
private
|
|
181
201
|
|
|
182
|
-
def invoke(callable, task,
|
|
202
|
+
def invoke(callable, task, continuation: nil, signal: nil, &block)
|
|
183
203
|
case callable
|
|
184
204
|
when Symbol
|
|
185
|
-
|
|
205
|
+
if signal
|
|
206
|
+
task.send(callable, signal, &block)
|
|
207
|
+
else
|
|
208
|
+
task.send(callable, &block)
|
|
209
|
+
end
|
|
186
210
|
when Proc
|
|
187
|
-
|
|
211
|
+
extras = continuation ? [continuation] : []
|
|
212
|
+
if signal
|
|
213
|
+
task.instance_exec(task, signal, *extras, &callable)
|
|
214
|
+
else
|
|
215
|
+
task.instance_exec(task, *extras, &callable)
|
|
216
|
+
end
|
|
188
217
|
else
|
|
189
|
-
|
|
218
|
+
args = signal ? [task, signal] : [task]
|
|
219
|
+
args << continuation if continuation
|
|
220
|
+
return callable.call(*args) if callable.respond_to?(:call)
|
|
190
221
|
|
|
191
222
|
raise ArgumentError, <<~MSG.chomp
|
|
192
223
|
callback must be a Symbol, Proc, or respond to #call (got #{callable.class}).
|
data/lib/cmdx/coercions.rb
CHANGED
data/lib/cmdx/fault.rb
CHANGED
|
@@ -124,5 +124,43 @@ module CMDx
|
|
|
124
124
|
@result.chain
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
+
# Enriches the stdlib detailed message with validation errors and
|
|
128
|
+
# propagation context for IRB and other debug formatters. {#message} is
|
|
129
|
+
# unchanged.
|
|
130
|
+
#
|
|
131
|
+
# @param highlight [Boolean] forwarded to `Exception#detailed_message`
|
|
132
|
+
# @param order [Symbol] forwarded to `Exception#detailed_message`
|
|
133
|
+
# @return [String]
|
|
134
|
+
def detailed_message(highlight: true, order: :top)
|
|
135
|
+
lines = [super]
|
|
136
|
+
append_errors_lines(lines)
|
|
137
|
+
append_origin_line(lines)
|
|
138
|
+
lines.join("\n")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @return [String] human-readable representation
|
|
142
|
+
def inspect
|
|
143
|
+
"#<#{self.class} task=#{task.name} tid=#{result.tid} cid=#{result.cid} xid=#{result.xid} reason=#{result.reason.inspect} errors=#{result.errors.to_h}>"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
def append_errors_lines(lines)
|
|
149
|
+
return if result.errors.empty?
|
|
150
|
+
|
|
151
|
+
lines << " errors:"
|
|
152
|
+
result.errors.each do |key, messages|
|
|
153
|
+
messages.each { |message| lines << " #{key}: #{message}" }
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def append_origin_line(lines)
|
|
158
|
+
origin = result.origin
|
|
159
|
+
return unless origin
|
|
160
|
+
|
|
161
|
+
reason = origin.reason || "no reason"
|
|
162
|
+
lines << " origin: #{origin.task.name} (#{reason})"
|
|
163
|
+
end
|
|
164
|
+
|
|
127
165
|
end
|
|
128
166
|
end
|
data/lib/cmdx/input.rb
CHANGED
|
@@ -22,6 +22,7 @@ module CMDx
|
|
|
22
22
|
# @option options [Symbol, Proc, #call] :if
|
|
23
23
|
# @option options [Symbol, Proc, #call] :unless
|
|
24
24
|
# @option options [Boolean] :required
|
|
25
|
+
# @option options [Boolean] :allow_nil (see {#allow_nil})
|
|
25
26
|
def initialize(name, children: EMPTY_ARRAY, **options)
|
|
26
27
|
@name = name.to_sym
|
|
27
28
|
@children = children.freeze
|
|
@@ -78,6 +79,14 @@ module CMDx
|
|
|
78
79
|
@options.fetch(:required, false)
|
|
79
80
|
end
|
|
80
81
|
|
|
82
|
+
# When `true`, a `nil` value after defaults skips coercion, transform, and
|
|
83
|
+
# validation. Per-validator `:allow_nil` only skips individual rules.
|
|
84
|
+
#
|
|
85
|
+
# @return [Boolean]
|
|
86
|
+
def allow_nil
|
|
87
|
+
@options.fetch(:allow_nil, false)
|
|
88
|
+
end
|
|
89
|
+
|
|
81
90
|
# Computed accessor/reader method name. Uses `:as` when provided,
|
|
82
91
|
# otherwise combines `:prefix`, `name`, and `:suffix` around the source.
|
|
83
92
|
#
|
|
@@ -131,7 +140,8 @@ module CMDx
|
|
|
131
140
|
# @note "Required" here means "the key is present in the source"; an
|
|
132
141
|
# explicit `nil` under an existing key satisfies the required check
|
|
133
142
|
# and is then routed through `:default`. Combine with `:presence` /
|
|
134
|
-
# `:validate` to reject explicit `nil` values
|
|
143
|
+
# `:validate` to reject explicit `nil` values, or `:allow_nil` (see
|
|
144
|
+
# {#allow_nil}) to leave `nil` unresolved.
|
|
135
145
|
#
|
|
136
146
|
# @param task [Task]
|
|
137
147
|
# @return [Object, nil] the resolved value (`nil` on failure)
|
|
@@ -189,7 +199,7 @@ module CMDx
|
|
|
189
199
|
end
|
|
190
200
|
|
|
191
201
|
value = apply_default(task) if value.nil?
|
|
192
|
-
return if value.nil? && !key_provided
|
|
202
|
+
return if value.nil? && (!key_provided || allow_nil)
|
|
193
203
|
|
|
194
204
|
@coercions ||= task.class.coercions.extract(@options)
|
|
195
205
|
value = task.class.coercions.coerce(task, accessor_name, value, @coercions)
|
data/lib/cmdx/inputs.rb
CHANGED
|
@@ -35,6 +35,7 @@ module CMDx
|
|
|
35
35
|
# @option options [Symbol, Proc, #call] :if
|
|
36
36
|
# @option options [Symbol, Proc, #call] :unless
|
|
37
37
|
# @option options [Boolean] :required
|
|
38
|
+
# @option options [Boolean] :allow_nil (see {Input#allow_nil})
|
|
38
39
|
# @option options [Object] :coerce forwarded with declaration (see {Coercions#extract})
|
|
39
40
|
# @option options [Object] :validate forwarded with declaration (see {Validators#extract})
|
|
40
41
|
# @return [Inputs] self for chaining
|
|
@@ -138,6 +139,7 @@ module CMDx
|
|
|
138
139
|
# @option options [Symbol, Proc, #call] :if
|
|
139
140
|
# @option options [Symbol, Proc, #call] :unless
|
|
140
141
|
# @option options [Boolean] :required
|
|
142
|
+
# @option options [Boolean] :allow_nil (see {Input#allow_nil})
|
|
141
143
|
# @option options [Object] :coerce forwarded with declaration (see {Coercions#extract})
|
|
142
144
|
# @option options [Object] :validate forwarded with declaration (see {Validators#extract})
|
|
143
145
|
# @yield nested child input DSL
|
|
@@ -162,6 +164,7 @@ module CMDx
|
|
|
162
164
|
# @option options [Symbol, Proc, #call] :transform mutator applied after coercion
|
|
163
165
|
# @option options [Symbol, Proc, #call] :if
|
|
164
166
|
# @option options [Symbol, Proc, #call] :unless
|
|
167
|
+
# @option options [Boolean] :allow_nil (see {Input#allow_nil})
|
|
165
168
|
# @option options [Object] :coerce forwarded with declaration (see {Coercions#extract})
|
|
166
169
|
# @option options [Object] :validate forwarded with declaration (see {Validators#extract})
|
|
167
170
|
# @yield nested child input DSL
|
|
@@ -185,6 +188,7 @@ module CMDx
|
|
|
185
188
|
# @option options [Symbol, Proc, #call] :transform mutator applied after coercion
|
|
186
189
|
# @option options [Symbol, Proc, #call] :if
|
|
187
190
|
# @option options [Symbol, Proc, #call] :unless
|
|
191
|
+
# @option options [Boolean] :allow_nil (see {Input#allow_nil})
|
|
188
192
|
# @option options [Object] :coerce forwarded with declaration (see {Coercions#extract})
|
|
189
193
|
# @option options [Object] :validate forwarded with declaration (see {Validators#extract})
|
|
190
194
|
# @yield nested child input DSL
|
data/lib/cmdx/runtime.rb
CHANGED
|
@@ -156,7 +156,8 @@ module CMDx
|
|
|
156
156
|
callbacks = @task.class.callbacks
|
|
157
157
|
return if callbacks.empty?
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
signal = Callbacks::POST_WORK_EVENTS.include?(event) ? @signal : nil
|
|
160
|
+
callbacks.process(event, @task, signal)
|
|
160
161
|
end
|
|
161
162
|
|
|
162
163
|
def run_around(event, &)
|
data/lib/cmdx/task.rb
CHANGED
|
@@ -274,6 +274,7 @@ module CMDx
|
|
|
274
274
|
# @option options [Symbol, Proc, #call] :if
|
|
275
275
|
# @option options [Symbol, Proc, #call] :unless
|
|
276
276
|
# @option options [Boolean] :required
|
|
277
|
+
# @option options [Boolean] :allow_nil (see {Input#allow_nil})
|
|
277
278
|
# @option options [Object] :coerce (see {Coercions#extract})
|
|
278
279
|
# @option options [Object] :validate (see {Validators#extract})
|
|
279
280
|
# @yield nested-input DSL block (see {Inputs::ChildBuilder})
|
|
@@ -307,6 +308,7 @@ module CMDx
|
|
|
307
308
|
# @option options [Symbol, Proc, #call] :transform mutator applied after coercion
|
|
308
309
|
# @option options [Symbol, Proc, #call] :if
|
|
309
310
|
# @option options [Symbol, Proc, #call] :unless
|
|
311
|
+
# @option options [Boolean] :allow_nil (see {Input#allow_nil})
|
|
310
312
|
# @option options [Object] :coerce (see {Coercions#extract})
|
|
311
313
|
# @option options [Object] :validate (see {Validators#extract})
|
|
312
314
|
# @yield nested-input DSL block (see {Inputs::ChildBuilder})
|
|
@@ -329,6 +331,7 @@ module CMDx
|
|
|
329
331
|
# @option options [Symbol, Proc, #call] :transform mutator applied after coercion
|
|
330
332
|
# @option options [Symbol, Proc, #call] :if
|
|
331
333
|
# @option options [Symbol, Proc, #call] :unless
|
|
334
|
+
# @option options [Boolean] :allow_nil (see {Input#allow_nil})
|
|
332
335
|
# @option options [Object] :coerce (see {Coercions#extract})
|
|
333
336
|
# @option options [Object] :validate (see {Validators#extract})
|
|
334
337
|
# @yield nested-input DSL block (see {Inputs::ChildBuilder})
|
data/lib/cmdx/version.rb
CHANGED
|
@@ -79,7 +79,8 @@ CMDx.configure do |config|
|
|
|
79
79
|
# Callbacks
|
|
80
80
|
# https://drexed.github.io/cmdx/configuration/#callbacks
|
|
81
81
|
# ===========================================================================
|
|
82
|
-
# Hook into a task's lifecycle.
|
|
82
|
+
# Hook into a task's lifecycle. Pre-work hooks receive the task; post-work
|
|
83
|
+
# hooks (`after_execution`, every `on_*`) also receive the finalized Signal.
|
|
83
84
|
#
|
|
84
85
|
# Available events:
|
|
85
86
|
# :before_execution, :before_validation,
|
|
@@ -88,8 +89,8 @@ CMDx.configure do |config|
|
|
|
88
89
|
# :on_success, :on_skipped, :on_failed,
|
|
89
90
|
# :on_ok, :on_ko
|
|
90
91
|
#
|
|
91
|
-
# config.callbacks.register(:on_failed, proc do |task|
|
|
92
|
-
# Rails.logger.error("[cmdx] #{task.class.name} failed: #{
|
|
92
|
+
# config.callbacks.register(:on_failed, proc do |task, signal|
|
|
93
|
+
# Rails.logger.error("[cmdx] #{task.class.name} failed: #{signal.reason}")
|
|
93
94
|
# end)
|
|
94
95
|
|
|
95
96
|
# ===========================================================================
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cmdx
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Juan Gomez
|
|
@@ -312,7 +312,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
312
312
|
- !ruby/object:Gem::Version
|
|
313
313
|
version: '0'
|
|
314
314
|
requirements: []
|
|
315
|
-
rubygems_version: 4.0.
|
|
315
|
+
rubygems_version: 4.0.17
|
|
316
316
|
specification_version: 4
|
|
317
317
|
summary: CMDx is a framework for building maintainable business processes.
|
|
318
318
|
test_files: []
|