gaskit 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec_status +118 -47
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +12 -101
- data/README.md +32 -2
- data/gaskit-0.1.0.gem +0 -0
- data/lib/gaskit/boot/query.rb +1 -1
- data/lib/gaskit/boot/service.rb +1 -1
- data/lib/gaskit/configuration.rb +12 -31
- data/lib/gaskit/contract_registry.rb +2 -2
- data/lib/gaskit/core.rb +8 -25
- data/lib/gaskit/flow.rb +334 -130
- data/lib/gaskit/flow_result.rb +19 -12
- data/lib/gaskit/helpers.rb +15 -0
- data/lib/gaskit/hook_registry.rb +62 -0
- data/lib/gaskit/hookable.rb +133 -0
- data/lib/gaskit/logger.rb +31 -19
- data/lib/gaskit/operation.rb +123 -59
- data/lib/gaskit/operation_exit.rb +5 -1
- data/lib/gaskit/operation_result.rb +6 -6
- data/lib/gaskit/version.rb +1 -1
- data/lib/gaskit.rb +1 -1
- metadata +5 -2
data/lib/gaskit/flow.rb
CHANGED
@@ -4,224 +4,428 @@ require "securerandom"
|
|
4
4
|
require_relative "core"
|
5
5
|
require_relative "flow_result"
|
6
6
|
require_relative "helpers"
|
7
|
+
require_relative "hookable"
|
7
8
|
|
8
9
|
module Gaskit
|
9
|
-
#
|
10
|
+
# The `Gaskit::Flow` class defines and executes a pipeline of operations,
|
11
|
+
# each of which returns a `Gaskit::OperationResult`. Flows can be defined via
|
12
|
+
# a class-based DSL or run inline using a block.
|
13
|
+
#
|
14
|
+
# Steps share context and flow state, with support for early exits, manual
|
15
|
+
# control, and step-by-step walking. A flow is constructed from a sequence of
|
16
|
+
# registered operations, each of which may receive and return input to influence
|
17
|
+
# subsequent steps.
|
18
|
+
#
|
19
|
+
# ## Features
|
20
|
+
# - Declarative or block-based step definitions
|
21
|
+
# - Per-step argument and context overrides
|
22
|
+
# - Shared flow-level context with metadata injection
|
23
|
+
# - Manual stepping (`walk` and `next_step`)
|
24
|
+
# - Rewind capability for retryable flows
|
25
|
+
# - Hookable lifecycle via `Gaskit::Hookable`
|
10
26
|
#
|
11
27
|
# @example Inline (block-based) flow
|
12
|
-
# result = Gaskit::Flow.call(1
|
13
|
-
# step
|
14
|
-
# step
|
28
|
+
# result = Gaskit::Flow.call(1) do
|
29
|
+
# step Add
|
30
|
+
# step Double
|
15
31
|
# end
|
16
32
|
#
|
33
|
+
# result.value # => 4
|
34
|
+
#
|
17
35
|
# @example Class-based flow
|
18
36
|
# class MyFlow < Gaskit::Flow
|
19
|
-
# step
|
20
|
-
# step
|
37
|
+
# step Add
|
38
|
+
# step Double
|
21
39
|
# end
|
22
40
|
#
|
23
|
-
# result = MyFlow.call(
|
41
|
+
# result = MyFlow.call(1)
|
42
|
+
# result.value # => 4
|
43
|
+
#
|
44
|
+
# @example Step-by-step execution (walk)
|
45
|
+
# flow = MyFlow.walk(1)
|
46
|
+
# while flow.has_next_step?
|
47
|
+
# flow.next_step
|
48
|
+
# end
|
49
|
+
# flow.result.value # => 4
|
50
|
+
#
|
51
|
+
# @example Rewinding and re-running
|
52
|
+
# flow.rewind
|
53
|
+
# flow.next_step # starts from first step again
|
54
|
+
#
|
55
|
+
# @see Gaskit::Operation
|
56
|
+
# @see Gaskit::FlowResult
|
57
|
+
# @see Gaskit::Hookable
|
24
58
|
class Flow
|
59
|
+
include Gaskit::Hookable
|
60
|
+
|
25
61
|
class << self
|
26
|
-
#
|
62
|
+
# Called when a subclass is defined, initializing an empty step list.
|
27
63
|
#
|
28
|
-
# @param subclass [Class]
|
64
|
+
# @param subclass [Class] the subclass inheriting from Flow
|
29
65
|
# @return [void]
|
30
66
|
def inherited(subclass)
|
31
|
-
subclass.instance_variable_set(:@
|
67
|
+
subclass.instance_variable_set(:@steps, @steps)
|
32
68
|
super
|
33
69
|
end
|
34
70
|
|
35
|
-
# Returns
|
71
|
+
# Returns the list of declared steps for the flow class.
|
36
72
|
#
|
37
|
-
# @return [Array<Array>] An array of [operation,
|
38
|
-
def
|
39
|
-
@
|
73
|
+
# @return [Array<Array>] An array of [operation, context, kwargs] triples.
|
74
|
+
def steps
|
75
|
+
@steps ||= []
|
40
76
|
end
|
41
77
|
|
42
|
-
#
|
78
|
+
# Registers a step in the class-level flow definition.
|
43
79
|
#
|
44
|
-
# @param operation [Class<Gaskit::Operation>] The operation
|
45
|
-
# @param
|
46
|
-
# @param
|
47
|
-
# @param kwargs [Hash] Keyword arguments for the step
|
80
|
+
# @param operation [Class<Gaskit::Operation>] The operation to run.
|
81
|
+
# @param context [Hash] Optional context for this step.
|
82
|
+
# @param kwargs [Hash] Keyword arguments for this step.
|
48
83
|
# @return [void]
|
49
|
-
def step(operation,
|
50
|
-
|
51
|
-
defined_steps << [operation, args, kwargs]
|
84
|
+
def step(operation, context: {}, **kwargs)
|
85
|
+
steps << [operation, context, kwargs]
|
52
86
|
end
|
53
87
|
|
54
|
-
# Executes the flow with soft
|
88
|
+
# Executes the flow with soft failure handling.
|
55
89
|
#
|
56
|
-
# @param args [Array] Positional arguments for the first step
|
57
|
-
# @param context [Hash] Shared context across all steps
|
58
|
-
# @param kwargs [Hash] Keyword arguments for the first step
|
59
|
-
# @return [FlowResult]
|
90
|
+
# @param args [Array] Positional arguments for the first step.
|
91
|
+
# @param context [Hash] Shared context across all steps.
|
92
|
+
# @param kwargs [Hash] Keyword arguments for the first step.
|
93
|
+
# @return [FlowResult] The result of the flow execution.
|
60
94
|
def call(*args, context: {}, **kwargs, &block)
|
61
95
|
invoke(false, context, *args, **kwargs, &block)
|
62
96
|
end
|
63
97
|
|
64
|
-
# Executes the flow with hard
|
98
|
+
# Executes the flow with hard failure handling (raises on unhandled errors).
|
65
99
|
#
|
66
|
-
# @param args [Array] Positional arguments for the first step
|
67
|
-
# @param context [Hash] Shared context across all steps
|
68
|
-
# @param kwargs [Hash] Keyword arguments for the first step
|
69
|
-
# @return [FlowResult]
|
100
|
+
# @param args [Array] Positional arguments for the first step.
|
101
|
+
# @param context [Hash] Shared context across all steps.
|
102
|
+
# @param kwargs [Hash] Keyword arguments for the first step.
|
103
|
+
# @return [FlowResult] The result of the flow execution.
|
104
|
+
# @raise [StandardError] If an error occurs in any step.
|
70
105
|
def call!(*args, context: {}, **kwargs, &block)
|
71
106
|
invoke(true, context, *args, **kwargs, &block)
|
72
107
|
end
|
73
108
|
|
74
|
-
|
109
|
+
# Creates a flow instance for step-by-step execution.
|
110
|
+
#
|
111
|
+
# @param args [Array] Initial positional arguments.
|
112
|
+
# @param context [Hash] Shared execution context.
|
113
|
+
# @param kwargs [Hash] Initial keyword arguments.
|
114
|
+
# @return [Flow] A walkable flow instance.
|
115
|
+
def walk(*args, context: {}, **kwargs)
|
116
|
+
build(false, context, *args, **kwargs)
|
117
|
+
end
|
75
118
|
|
76
|
-
#
|
119
|
+
# Same as {#walk} but raises on any step failure.
|
120
|
+
#
|
121
|
+
# @param args [Array] Initial positional arguments.
|
122
|
+
# @param context [Hash] Shared execution context.
|
123
|
+
# @param kwargs [Hash] Initial keyword arguments.
|
124
|
+
# @return [Flow] A walkable flow instance with hard failure behavior.
|
125
|
+
def walk!(*args, context: {}, **kwargs)
|
126
|
+
build(true, context, *args, **kwargs)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Internal flow execution logic.
|
130
|
+
#
|
131
|
+
# @param raise_on_failure [Boolean] Whether to raise on failure.
|
132
|
+
# @param context [Hash] Execution context.
|
133
|
+
# @param args [Array] Initial positional arguments.
|
134
|
+
# @param kwargs [Hash] Initial keyword arguments.
|
135
|
+
# @return [FlowResult] The result of the executed flow.
|
77
136
|
def invoke(raise_on_failure, context, *args, **kwargs, &block)
|
78
|
-
flow =
|
79
|
-
flow.execute
|
137
|
+
flow = build(raise_on_failure, context, *args, **kwargs, &block)
|
138
|
+
flow.send(:execute, &block)
|
80
139
|
end
|
81
|
-
end
|
82
140
|
|
83
|
-
|
84
|
-
attr_reader :context
|
141
|
+
private
|
85
142
|
|
86
|
-
|
87
|
-
|
143
|
+
# Constructs a flow instance.
|
144
|
+
#
|
145
|
+
# @param raise_on_failure [Boolean] Whether the flow should raise on failure.
|
146
|
+
# @param context [Hash] Flow context.
|
147
|
+
# @param args [Array] Positional args.
|
148
|
+
# @param kwargs [Hash] Keyword args.
|
149
|
+
# @return [Flow] The constructed flow instance.
|
150
|
+
def build(raise_on_failure, context, *args, **kwargs)
|
151
|
+
new(raise_on_failure, context, *args, **kwargs)
|
152
|
+
end
|
153
|
+
end
|
88
154
|
|
89
|
-
|
90
|
-
attr_reader :steps
|
155
|
+
attr_reader :logger
|
91
156
|
|
92
|
-
#
|
157
|
+
# Returns true if there are more steps remaining in the sequence.
|
93
158
|
#
|
94
|
-
# @
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
# @return [void]
|
99
|
-
def step(operation, *args, context: {}, **kwargs, &block)
|
100
|
-
raise ArgumentError, "Operation must be a subclass of Gaskit::Operation" unless operation <= Gaskit::Operation
|
159
|
+
# @return [Boolean] Whether the flow has more steps to execute.
|
160
|
+
def next_step?
|
161
|
+
@step_index < @step_sequence.size
|
162
|
+
end
|
101
163
|
|
102
|
-
|
164
|
+
# Returns the metadata for the pending step.
|
165
|
+
#
|
166
|
+
# @return [Hash, nil] The pending step's operation, context, and kwargs, or nil if no steps remain.
|
167
|
+
def pending_step(*args, **kwargs)
|
168
|
+
return nil unless next_step?
|
103
169
|
|
104
|
-
|
105
|
-
|
106
|
-
|
170
|
+
operation, context, step_kwargs = @step_sequence[@step_index]
|
171
|
+
context = @context.merge(context)
|
172
|
+
args, kwargs = resolve_step_input(
|
173
|
+
args: args,
|
174
|
+
kwargs: kwargs,
|
175
|
+
step_kwargs: step_kwargs
|
176
|
+
)
|
107
177
|
|
108
|
-
|
178
|
+
{ operation: operation, context: context, args: args, kwargs: kwargs }
|
109
179
|
end
|
110
180
|
|
111
|
-
# Executes the
|
181
|
+
# Executes the next step in the flow.
|
112
182
|
#
|
113
|
-
# @
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
else
|
119
|
-
self.class.defined_steps.each { |(op, args, kwargs)| step(op, *args, **kwargs) }
|
120
|
-
end
|
183
|
+
# @param args [Array] Optional positional arguments to override input.
|
184
|
+
# @param kwargs [Hash] Optional keyword arguments to override input.
|
185
|
+
# @return [Gaskit::OperationResult, nil] The result of the step, or nil if no steps remain.
|
186
|
+
def next_step(*args, **kwargs)
|
187
|
+
return unless next_step?
|
121
188
|
|
122
|
-
|
123
|
-
|
189
|
+
operation, context, step_kwargs = @step_sequence[@step_index]
|
190
|
+
@step_index += 1
|
191
|
+
|
192
|
+
process_step(operation, context, step_kwargs, args, kwargs)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Runs a step inline from within a block-based flow definition.
|
196
|
+
#
|
197
|
+
# @param operation [Class<Gaskit::Operation>] The operation to execute.
|
198
|
+
# @param context [Hash] Additional context for the step.
|
199
|
+
# @param kwargs [Hash] Keyword arguments for the step.
|
200
|
+
# @return [Gaskit::OperationResult] The result of the step.
|
201
|
+
def step(operation, context: {}, **kwargs)
|
202
|
+
process_step(operation, context, kwargs)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Rewinds the flow to its initial state.
|
206
|
+
#
|
207
|
+
# @return [void]
|
208
|
+
def rewind
|
209
|
+
@input = @initial_input.dup
|
210
|
+
@result = nil
|
211
|
+
@steps.clear
|
212
|
+
@step_index = 0
|
213
|
+
end
|
124
214
|
|
125
|
-
|
215
|
+
# Returns the result hashes from all executed steps.
|
216
|
+
#
|
217
|
+
# @return [Array<Hash>] Array of step metadata hashes including input/output.
|
218
|
+
def results
|
219
|
+
@steps.map { |entry| entry[:result] }
|
126
220
|
end
|
127
221
|
|
128
222
|
private
|
129
223
|
|
130
|
-
# Initializes a flow instance
|
224
|
+
# Initializes a new flow instance.
|
131
225
|
#
|
132
|
-
# @param raise_on_failure [Boolean] Whether to raise on
|
133
|
-
# @param context [Hash] Flow context
|
134
|
-
# @param
|
135
|
-
|
226
|
+
# @param raise_on_failure [Boolean] Whether to raise on step failure.
|
227
|
+
# @param context [Hash] Flow execution context.
|
228
|
+
# @param args [Array] Initial positional arguments.
|
229
|
+
# @param kwargs [Hash] Initial keyword arguments.
|
230
|
+
def initialize(raise_on_failure, context, *args, **kwargs)
|
136
231
|
@raise_on_failure = raise_on_failure
|
137
232
|
@context = apply_context(context)
|
138
|
-
|
139
|
-
@
|
233
|
+
|
234
|
+
@input = [args, kwargs]
|
235
|
+
@initial_input = @input.dup
|
140
236
|
@result = nil
|
237
|
+
|
238
|
+
@steps = []
|
239
|
+
@step_sequence = self.class.steps.dup
|
240
|
+
@step_index = 0
|
241
|
+
|
242
|
+
@logger = Gaskit::Logger.new(self, context: @context)
|
141
243
|
end
|
142
244
|
|
143
|
-
#
|
144
|
-
# and injects the `gaskit_flow` key to indicate to operations they are a part
|
145
|
-
# of a flow.
|
245
|
+
# Executes a single step of the flow.
|
146
246
|
#
|
147
|
-
# @param
|
148
|
-
# @
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
247
|
+
# @param operation [Class<Gaskit::Operation>] The operation to execute.
|
248
|
+
# @param context [Hash] Step-local context.
|
249
|
+
# @param step_kwargs [Hash] DSL-defined kwargs.
|
250
|
+
# @param override_args [Array] Positional args from manual call.
|
251
|
+
# @param override_kwargs [Hash] Kwargs from manual call.
|
252
|
+
# @return [Gaskit::OperationResult] The result object.
|
253
|
+
def process_step(operation, context, step_kwargs, override_args = [], override_kwargs = {})
|
254
|
+
raise ArgumentError, "Operation must be a subclass of Gaskit::Operation" unless operation <= Gaskit::Operation
|
255
|
+
return if @result&.early_exit?
|
256
|
+
|
257
|
+
args, kwargs = resolve_step_input(
|
258
|
+
args: override_args,
|
259
|
+
kwargs: override_kwargs,
|
260
|
+
step_kwargs: step_kwargs
|
154
261
|
)
|
155
262
|
|
156
|
-
|
263
|
+
kwargs = kwargs.merge(context: context)
|
264
|
+
|
265
|
+
@result = execute_step(operation, context, args, kwargs)
|
266
|
+
@steps << step_entry(operation, args, kwargs)
|
267
|
+
@input = next_step_input || [[], {}]
|
268
|
+
|
269
|
+
@result
|
157
270
|
end
|
158
271
|
|
159
|
-
#
|
272
|
+
# Resolves arguments for a step, merging flow input and overrides.
|
160
273
|
#
|
161
|
-
# @param
|
162
|
-
# @param kwargs [Hash]
|
163
|
-
# @
|
164
|
-
|
274
|
+
# @param args [Array] Overriding args.
|
275
|
+
# @param kwargs [Hash] Overriding kwargs.
|
276
|
+
# @param step_kwargs [Hash] Default step keyword args.
|
277
|
+
# @return [Array<Array, Hash>] Final [args, kwargs] pair.
|
278
|
+
def resolve_step_input(args: [], kwargs: {}, step_kwargs: {})
|
165
279
|
input_args, input_kwargs = @input
|
166
|
-
kwargs = (input_kwargs || {}).merge(kwargs).merge(context: @context)
|
167
280
|
|
168
|
-
|
281
|
+
args = input_args if args.empty?
|
282
|
+
kwargs = input_kwargs.merge(step_kwargs).merge(kwargs)
|
283
|
+
|
284
|
+
[args, kwargs]
|
285
|
+
end
|
169
286
|
|
170
|
-
|
171
|
-
|
172
|
-
|
287
|
+
# Executes all steps in the flow.
|
288
|
+
#
|
289
|
+
# @return [FlowResult] The result of the executed flow.
|
290
|
+
def execute(&block)
|
291
|
+
duration, (_, error) = time_execution(&block)
|
292
|
+
result = build_result(duration, error)
|
173
293
|
|
174
|
-
|
175
|
-
|
294
|
+
begin
|
295
|
+
apply_after_hooks(result)
|
296
|
+
rescue StandardError => e
|
297
|
+
result = handle_after_hook_error(e, duration)
|
298
|
+
end
|
299
|
+
|
300
|
+
result
|
301
|
+
end
|
302
|
+
|
303
|
+
# Executes a specific operation step.
|
304
|
+
#
|
305
|
+
# @param operation [Class<Gaskit::Operation>] The operation to run.
|
306
|
+
# @param context [Hash] Execution context for the operation.
|
307
|
+
# @param args [Array] Positional arguments.
|
308
|
+
# @param kwargs [Hash] Keyword arguments.
|
309
|
+
# @return [Gaskit::OperationResult] Result of the operation call.
|
310
|
+
def execute_step(operation, context, args, kwargs, &block)
|
311
|
+
raise ArgumentError, "Operation must be a subclass of Gaskit::Operation" unless operation <= Gaskit::Operation
|
312
|
+
|
313
|
+
context = @context.merge(context)
|
314
|
+
return operation.call!(*args, context: context, **kwargs, &block) if @raise_on_failure
|
315
|
+
|
316
|
+
operation.call(*args, context: context, **kwargs, &block)
|
176
317
|
end
|
177
318
|
|
178
|
-
#
|
319
|
+
# Times flow execution, including any hooks set.
|
179
320
|
#
|
180
|
-
# @
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
321
|
+
# @return [Array<Float, Object>] Execution time and final result.
|
322
|
+
def time_execution(&block)
|
323
|
+
Helpers.time_execution do
|
324
|
+
apply_hooks(:before, :around) do
|
325
|
+
if block_given?
|
326
|
+
instance_eval(&block)
|
327
|
+
else
|
328
|
+
@step_sequence.each { next_step }
|
329
|
+
end
|
330
|
+
|
331
|
+
[@result, nil]
|
332
|
+
end
|
333
|
+
rescue StandardError => e
|
334
|
+
handle_execution_error(e)
|
335
|
+
[nil, e]
|
336
|
+
end
|
337
|
+
end
|
186
338
|
|
339
|
+
# Merges default and passed context and injects flow metadata.
|
340
|
+
#
|
341
|
+
# @param context [Hash] User-provided context.
|
342
|
+
# @return [Hash] Final context.
|
343
|
+
def apply_context(context)
|
344
|
+
default_context = Gaskit.configuration.context_provider.call
|
345
|
+
context = default_context.merge(
|
346
|
+
gaskit_flow: { id: SecureRandom.uuid, name: Gaskit::Helpers.resolve_name(self) },
|
347
|
+
**context
|
348
|
+
)
|
349
|
+
|
350
|
+
Helpers.deep_compact(context)
|
351
|
+
end
|
352
|
+
|
353
|
+
# Builds a step metadata entry.
|
354
|
+
#
|
355
|
+
# @param operation [Class] Operation class.
|
356
|
+
# @param args [Array] Arguments passed.
|
357
|
+
# @param kwargs [Hash] Keyword arguments passed.
|
358
|
+
# @return [Hash] Metadata about the step.
|
359
|
+
def step_entry(operation, args, kwargs)
|
187
360
|
{
|
188
361
|
operation: operation,
|
189
362
|
args: args,
|
190
363
|
kwargs: kwargs,
|
191
|
-
result: result.to_h
|
364
|
+
result: @result.to_h
|
192
365
|
}
|
193
366
|
end
|
194
367
|
|
195
|
-
#
|
368
|
+
# Determines next input tuple based on previous result.
|
196
369
|
#
|
197
|
-
# @
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
370
|
+
# @return [Array<Array, Hash>, nil] Tuple of [args, kwargs] or nil if step failed.
|
371
|
+
def next_step_input
|
372
|
+
case @result&.value
|
373
|
+
when Array
|
374
|
+
[@result&.value, {}]
|
375
|
+
when Hash
|
376
|
+
[[], @result&.value]
|
377
|
+
else
|
378
|
+
[[@result&.value], {}]
|
379
|
+
end
|
380
|
+
end
|
204
381
|
|
205
|
-
|
382
|
+
# Builds a FlowResult object.
|
383
|
+
#
|
384
|
+
# @param duration [Float] Time spent executing.
|
385
|
+
# @param error [StandardError, nil] Optional error object.
|
386
|
+
# @return [FlowResult] The constructed result object.
|
387
|
+
def build_result(duration, error = nil)
|
388
|
+
error ||= @result&.error
|
389
|
+
|
390
|
+
FlowResult.new(
|
391
|
+
error.nil?,
|
392
|
+
@result&.value,
|
393
|
+
error,
|
394
|
+
steps: @steps,
|
395
|
+
duration: duration,
|
396
|
+
context: @context
|
397
|
+
)
|
206
398
|
end
|
207
399
|
|
208
|
-
#
|
209
|
-
# is a failure or has a nil value.
|
400
|
+
# Handles a raised exception during execution.
|
210
401
|
#
|
211
|
-
# @param
|
402
|
+
# @param error [StandardError] The raised error.
|
212
403
|
# @return [void]
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
@
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
404
|
+
# @raise [StandardError] If raise_on_failure is true.
|
405
|
+
def handle_execution_error(error)
|
406
|
+
log_exception(error)
|
407
|
+
raise error if @raise_on_failure
|
408
|
+
end
|
409
|
+
|
410
|
+
# Handles a post-hook exception.
|
411
|
+
#
|
412
|
+
# @param error [StandardError] The raised error.
|
413
|
+
# @param duration [Float] Total flow duration.
|
414
|
+
# @return [FlowResult] A failed FlowResult.
|
415
|
+
def handle_after_hook_error(error, duration)
|
416
|
+
log_exception(error)
|
417
|
+
raise error if @raise_on_failure
|
418
|
+
|
419
|
+
build_result(duration, error)
|
420
|
+
end
|
421
|
+
|
422
|
+
# Logs an exception with context.
|
423
|
+
#
|
424
|
+
# @param exception [StandardError] Exception to log.
|
425
|
+
# @return [void]
|
426
|
+
def log_exception(exception)
|
427
|
+
logger.error { "[#{exception.class}] #{exception.message}" }
|
428
|
+
# logger.error { exception.backtrace&.join("\n") }
|
225
429
|
end
|
226
430
|
end
|
227
431
|
end
|
data/lib/gaskit/flow_result.rb
CHANGED
@@ -15,21 +15,28 @@ module Gaskit
|
|
15
15
|
attr_reader :steps
|
16
16
|
|
17
17
|
# Initializes a new FlowResult
|
18
|
-
#
|
19
|
-
# @param
|
20
|
-
# @param
|
21
|
-
# @param
|
22
|
-
# @param
|
23
|
-
|
18
|
+
# .
|
19
|
+
# @param success [Boolean] If the flow was successful or not
|
20
|
+
# @param value [Object, nil] The final operation result
|
21
|
+
# @param error [StandardError, nil] The error encountered during the operation.
|
22
|
+
# @param options [Hash] Keyword arguments
|
23
|
+
# @option options [Array<Hash>] :steps Step-by-step execution details
|
24
|
+
# @option options [Float, String] :duration Total flow duration
|
25
|
+
# @option options [Hash] Execution context
|
26
|
+
def initialize(success, value, error = nil, **options)
|
24
27
|
super(
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
duration: duration,
|
29
|
-
context: context
|
28
|
+
success,
|
29
|
+
value,
|
30
|
+
error,
|
31
|
+
duration: options[:duration],
|
32
|
+
context: options[:context]
|
30
33
|
)
|
31
34
|
|
32
|
-
@steps = steps
|
35
|
+
@steps = options.fetch(:steps, [])
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_h
|
39
|
+
super.merge(steps: steps)
|
33
40
|
end
|
34
41
|
end
|
35
42
|
end
|
data/lib/gaskit/helpers.rb
CHANGED
@@ -25,6 +25,21 @@ module Gaskit
|
|
25
25
|
result[k.to_sym] = compacted unless compacted.nil?
|
26
26
|
end
|
27
27
|
end
|
28
|
+
|
29
|
+
# Resolves the provide class's name.
|
30
|
+
#
|
31
|
+
# @param source [Class, Object, String, Symbol]
|
32
|
+
# @return [String] The resolved class name.
|
33
|
+
def resolve_name(source)
|
34
|
+
case source
|
35
|
+
when String, Symbol
|
36
|
+
source.to_s
|
37
|
+
when Class
|
38
|
+
source.name
|
39
|
+
else
|
40
|
+
source.class.name
|
41
|
+
end
|
42
|
+
end
|
28
43
|
end
|
29
44
|
end
|
30
45
|
end
|