axn 0.1.0.pre.alpha.2.7 → 0.1.0.pre.alpha.2.8
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/.rubocop.yml +11 -5
- data/CHANGELOG.md +14 -1
- data/Rakefile +12 -0
- data/docs/intro/about.md +2 -2
- data/docs/intro/overview.md +18 -0
- data/docs/recipes/rubocop-integration.md +352 -0
- data/docs/reference/action-result.md +2 -2
- data/docs/reference/class.md +122 -6
- data/docs/reference/configuration.md +43 -9
- data/docs/reference/instance.md +0 -52
- data/docs/usage/setup.md +4 -0
- data/docs/usage/steps.md +335 -0
- data/docs/usage/writing.md +70 -3
- data/lib/action/attachable/steps.rb +18 -17
- data/lib/action/attachable/subactions.rb +1 -1
- data/lib/action/context.rb +10 -14
- data/lib/action/core/context/facade.rb +11 -2
- data/lib/action/core/context/facade_inspector.rb +3 -2
- data/lib/action/core/context/internal.rb +3 -11
- data/lib/action/core/contract_validation.rb +1 -1
- data/lib/action/core/flow/callbacks.rb +27 -12
- data/lib/action/core/flow/exception_execution.rb +2 -5
- data/lib/action/core/flow/handlers/{base_handler.rb → base_descriptor.rb} +7 -4
- data/lib/action/core/flow/handlers/descriptors/callback_descriptor.rb +17 -0
- data/lib/action/core/flow/handlers/descriptors/message_descriptor.rb +53 -0
- data/lib/action/core/flow/handlers/matcher.rb +41 -2
- data/lib/action/core/flow/handlers/resolvers/base_resolver.rb +28 -0
- data/lib/action/core/flow/handlers/resolvers/callback_resolver.rb +29 -0
- data/lib/action/core/flow/handlers/resolvers/message_resolver.rb +59 -0
- data/lib/action/core/flow/handlers.rb +7 -4
- data/lib/action/core/flow/messages.rb +15 -41
- data/lib/action/core/nesting_tracking.rb +31 -0
- data/lib/action/core/timing.rb +1 -1
- data/lib/action/core.rb +20 -12
- data/lib/action/exceptions.rb +20 -2
- data/lib/action/result.rb +30 -32
- data/lib/axn/factory.rb +22 -23
- data/lib/axn/rubocop.rb +10 -0
- data/lib/axn/version.rb +1 -1
- data/lib/rubocop/cop/axn/README.md +237 -0
- data/lib/rubocop/cop/axn/unchecked_result.rb +327 -0
- metadata +14 -6
- data/lib/action/core/flow/handlers/callback_handler.rb +0 -21
- data/lib/action/core/flow/handlers/message_handler.rb +0 -27
- data/lib/action/core/hoist_errors.rb +0 -58
data/lib/action/context.rb
CHANGED
@@ -11,28 +11,24 @@ module Action
|
|
11
11
|
# Framework-managed fields
|
12
12
|
@failure = false
|
13
13
|
@exception = nil
|
14
|
-
@error_from_user = nil
|
15
|
-
@error_prefix = nil
|
16
14
|
@elapsed_time = nil
|
17
15
|
end
|
18
16
|
|
19
|
-
def fail!(message = nil)
|
20
|
-
@error_from_user = message if message.present?
|
21
|
-
raise Action::Failure, message
|
22
|
-
end
|
23
|
-
|
24
|
-
# INTERNAL: base for further filtering (for logging) or providing user with usage hints
|
25
|
-
def __combined_data = @provided_data.merge(@exposed_data)
|
26
|
-
|
27
17
|
# Framework state methods
|
28
18
|
def ok? = !@failure
|
29
19
|
def failed? = @failure || false
|
30
20
|
|
31
21
|
# Framework field accessors
|
32
|
-
attr_accessor :
|
22
|
+
attr_accessor :elapsed_time
|
23
|
+
attr_reader :exception
|
24
|
+
private :elapsed_time=
|
33
25
|
|
34
|
-
#
|
35
|
-
|
36
|
-
|
26
|
+
# INTERNAL: base for further filtering (for logging) or providing user with usage hints
|
27
|
+
def __combined_data = @provided_data.merge(@exposed_data)
|
28
|
+
|
29
|
+
def __record_exception(e)
|
30
|
+
@exception = e
|
31
|
+
@failure = true
|
32
|
+
end
|
37
33
|
end
|
38
34
|
end
|
@@ -15,7 +15,7 @@ module Action
|
|
15
15
|
|
16
16
|
(@declared_fields + Array(implicitly_allowed_fields)).each do |field|
|
17
17
|
singleton_class.define_method(field) do
|
18
|
-
|
18
|
+
_context_data_source[field]
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -34,6 +34,15 @@ module Action
|
|
34
34
|
|
35
35
|
def action_name = @action.class.name.presence || "The action"
|
36
36
|
|
37
|
-
def
|
37
|
+
def _context_data_source = raise NotImplementedError
|
38
|
+
|
39
|
+
def _msg_resolver(event_type, exception:)
|
40
|
+
Action::Core::Flow::Handlers::Resolvers::MessageResolver.new(
|
41
|
+
action._messages_registry,
|
42
|
+
event_type,
|
43
|
+
action:,
|
44
|
+
exception:,
|
45
|
+
)
|
46
|
+
end
|
38
47
|
end
|
39
48
|
end
|
@@ -22,8 +22,9 @@ module Action
|
|
22
22
|
return unless facade.is_a?(Action::Result)
|
23
23
|
|
24
24
|
return "[OK]" if context.ok?
|
25
|
-
|
26
|
-
|
25
|
+
|
26
|
+
if context.exception.is_a?(Action::Failure)
|
27
|
+
return context.exception.message.present? ? "[failed with '#{context.exception.message}']" : "[failed]"
|
27
28
|
end
|
28
29
|
|
29
30
|
%([failed with #{context.exception.class.name}: '#{context.exception.message}'])
|
@@ -5,20 +5,12 @@ require "action/core/context/facade"
|
|
5
5
|
module Action
|
6
6
|
# Inbound / Internal ContextFacade
|
7
7
|
class InternalContext < ContextFacade
|
8
|
-
|
9
|
-
def
|
10
|
-
msg = action.class._static_message_for(:error, action:, exception: @context.exception || Action::Failure.new)
|
11
|
-
[@context.error_prefix, msg.presence || "Something went wrong"].compact.join(" ").squeeze(" ")
|
12
|
-
end
|
13
|
-
|
14
|
-
def default_success
|
15
|
-
msg = action.class._static_message_for(:success, action:, exception: nil)
|
16
|
-
msg.presence || "Action completed successfully"
|
17
|
-
end
|
8
|
+
def default_error = _msg_resolver(:error, exception: Action::Failure.new).resolve_default_message
|
9
|
+
def default_success = _msg_resolver(:success, exception: nil).resolve_default_message
|
18
10
|
|
19
11
|
private
|
20
12
|
|
21
|
-
def
|
13
|
+
def _context_data_source = @context.provided_data
|
22
14
|
|
23
15
|
def method_missing(method_name, ...) # rubocop:disable Style/MissingRespondToMissing (because we're not actually responding to anything additional)
|
24
16
|
if @context.__combined_data.key?(method_name.to_sym)
|
@@ -13,7 +13,7 @@ module Action
|
|
13
13
|
new_value = config.preprocess.call(initial_value)
|
14
14
|
@__context.provided_data[config.field] = new_value
|
15
15
|
rescue StandardError => e
|
16
|
-
raise Action::ContractViolation::PreprocessingError, "Error preprocessing field '#{config.field}': #{e.message}"
|
16
|
+
raise Action::ContractViolation::PreprocessingError, "Error preprocessing field '#{config.field}': #{e.message}", cause: e
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "action/core/flow/handlers"
|
4
|
+
require "action/core/flow/handlers/resolvers/callback_resolver"
|
4
5
|
|
5
6
|
module Action
|
6
7
|
module Core
|
@@ -17,35 +18,49 @@ module Action
|
|
17
18
|
module ClassMethods
|
18
19
|
# Internal dispatcher
|
19
20
|
def _dispatch_callbacks(event_type, action:, exception: nil)
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
resolver = Action::Core::Flow::Handlers::Resolvers::CallbackResolver.new(
|
22
|
+
_callbacks_registry,
|
23
|
+
event_type,
|
24
|
+
action:,
|
25
|
+
exception:,
|
26
|
+
)
|
27
|
+
resolver.execute_callbacks
|
23
28
|
end
|
24
29
|
|
25
30
|
# ONLY raised exceptions (i.e. NOT fail!).
|
26
|
-
def on_exception(**, &block) = _add_callback(:exception, **, block:)
|
31
|
+
def on_exception(handler = nil, **, &block) = _add_callback(:exception, handler:, **, block:)
|
27
32
|
|
28
33
|
# ONLY raised on fail! (i.e. NOT unhandled exceptions).
|
29
|
-
def on_failure(**, &block) = _add_callback(:failure, **, block:)
|
34
|
+
def on_failure(handler = nil, **, &block) = _add_callback(:failure, handler:, **, block:)
|
30
35
|
|
31
36
|
# Handles both fail! and unhandled exceptions
|
32
|
-
def on_error(**, &block) = _add_callback(:error, **, block:)
|
37
|
+
def on_error(handler = nil, **, &block) = _add_callback(:error, handler:, **, block:)
|
33
38
|
|
34
39
|
# Executes when the action completes successfully (after all after hooks complete successfully)
|
35
40
|
# Runs in child-first order (child handlers before parent handlers)
|
36
|
-
def on_success(**, &block) = _add_callback(:success, **, block:)
|
41
|
+
def on_success(handler = nil, **, &block) = _add_callback(:success, handler:, **, block:)
|
37
42
|
|
38
43
|
private
|
39
44
|
|
40
|
-
def _add_callback(event_type, block
|
45
|
+
def _add_callback(event_type, handler: nil, block: nil, **kwargs)
|
41
46
|
raise ArgumentError, "on_#{event_type} cannot be called with both :if and :unless" if kwargs.key?(:if) && kwargs.key?(:unless)
|
47
|
+
raise ArgumentError, "on_#{event_type} cannot be called with both a block and a handler" if block && handler
|
48
|
+
raise ArgumentError, "on_#{event_type} must be called with a block or symbol" unless block || handler
|
42
49
|
|
43
|
-
|
44
|
-
|
50
|
+
# If handler is already a descriptor, use it directly
|
51
|
+
entry = if handler.is_a?(Action::Core::Flow::Handlers::Descriptors::CallbackDescriptor)
|
52
|
+
raise ArgumentError, "Cannot pass additional configuration with prebuilt descriptor" if kwargs.any? || block
|
53
|
+
|
54
|
+
handler
|
55
|
+
else
|
56
|
+
Action::Core::Flow::Handlers::Descriptors::CallbackDescriptor.build(
|
57
|
+
handler: handler || block,
|
58
|
+
**kwargs,
|
59
|
+
)
|
60
|
+
end
|
45
61
|
|
46
|
-
matcher = condition.nil? ? nil : Action::Core::Flow::Handlers::Matcher.new(condition, invert: kwargs.key?(:unless))
|
47
|
-
entry = Action::Core::Flow::Handlers::CallbackHandler.new(matcher:, handler: block)
|
48
62
|
self._callbacks_registry = _callbacks_registry.register(event_type:, entry:)
|
63
|
+
true
|
49
64
|
end
|
50
65
|
end
|
51
66
|
end
|
@@ -33,6 +33,8 @@ module Action
|
|
33
33
|
def _with_exception_handling
|
34
34
|
yield
|
35
35
|
rescue StandardError => e
|
36
|
+
@__context.__record_exception(e)
|
37
|
+
|
36
38
|
# on_error handlers run for both unhandled exceptions and fail!
|
37
39
|
self.class._dispatch_callbacks(:error, action: self, exception: e)
|
38
40
|
|
@@ -42,12 +44,7 @@ module Action
|
|
42
44
|
else
|
43
45
|
# on_exception handlers run for ONLY for unhandled exceptions.
|
44
46
|
_trigger_on_exception(e)
|
45
|
-
|
46
|
-
@__context.exception = e
|
47
47
|
end
|
48
|
-
|
49
|
-
# Set failure state using accessor method
|
50
|
-
@__context.send(:failure=, true)
|
51
48
|
end
|
52
49
|
|
53
50
|
def try
|
@@ -8,15 +8,15 @@ module Action
|
|
8
8
|
# "Handlers" doesn't feel like *quite* the right name for this, but basically things in this namespace
|
9
9
|
# relate to conditionally-invoked code blocks (e.g. callbacks, messages, etc.)
|
10
10
|
module Handlers
|
11
|
-
class
|
11
|
+
class BaseDescriptor
|
12
12
|
def initialize(matcher: nil, handler: nil)
|
13
13
|
@matcher = matcher
|
14
14
|
@handler = handler
|
15
15
|
end
|
16
16
|
|
17
|
-
attr_reader :handler
|
17
|
+
attr_reader :handler, :matcher
|
18
18
|
|
19
|
-
def static? = @matcher.nil?
|
19
|
+
def static? = @matcher.nil? || @matcher.static?
|
20
20
|
|
21
21
|
def matches?(action:, exception:)
|
22
22
|
return true if static?
|
@@ -24,7 +24,10 @@ module Action
|
|
24
24
|
@matcher.call(exception:, action:)
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
def self.build(handler: nil, if: nil, unless: nil, **)
|
28
|
+
matcher = Matcher.build(if:, unless:)
|
29
|
+
new(matcher:, handler:)
|
30
|
+
end
|
28
31
|
end
|
29
32
|
end
|
30
33
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action/core/flow/handlers/base_descriptor"
|
4
|
+
|
5
|
+
module Action
|
6
|
+
module Core
|
7
|
+
module Flow
|
8
|
+
module Handlers
|
9
|
+
module Descriptors
|
10
|
+
# Data structure for callback configuration - no behavior, just data
|
11
|
+
class CallbackDescriptor < BaseDescriptor
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action/core/flow/handlers/base_descriptor"
|
4
|
+
|
5
|
+
module Action
|
6
|
+
module Core
|
7
|
+
module Flow
|
8
|
+
module Handlers
|
9
|
+
module Descriptors
|
10
|
+
# Data structure for message configuration - no behavior, just data
|
11
|
+
class MessageDescriptor < BaseDescriptor
|
12
|
+
attr_reader :prefix
|
13
|
+
|
14
|
+
def initialize(matcher:, handler:, prefix: nil)
|
15
|
+
super(matcher:, handler:)
|
16
|
+
@prefix = prefix
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.build(handler: nil, if: nil, unless: nil, prefix: nil, from: nil, **)
|
20
|
+
new(
|
21
|
+
handler:,
|
22
|
+
prefix:,
|
23
|
+
matcher: _build_matcher(if:, unless:, from:),
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self._build_matcher(if:, unless:, from:)
|
28
|
+
rules = [
|
29
|
+
binding.local_variable_get(:if),
|
30
|
+
binding.local_variable_get(:unless),
|
31
|
+
_build_rule_for_from_condition(from),
|
32
|
+
].compact
|
33
|
+
|
34
|
+
Action::Core::Flow::Handlers::Matcher.new(rules, invert: !!binding.local_variable_get(:unless))
|
35
|
+
end
|
36
|
+
|
37
|
+
def self._build_rule_for_from_condition(from_class)
|
38
|
+
return nil unless from_class
|
39
|
+
|
40
|
+
if from_class.is_a?(String)
|
41
|
+
lambda { |exception:, **|
|
42
|
+
exception.is_a?(Action::Failure) && exception.source&.class&.name == from_class
|
43
|
+
}
|
44
|
+
else
|
45
|
+
->(exception:, **) { exception.is_a?(Action::Failure) && exception.source.is_a?(from_class) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -6,14 +6,15 @@ module Action
|
|
6
6
|
module Core
|
7
7
|
module Flow
|
8
8
|
module Handlers
|
9
|
-
class
|
9
|
+
class SingleRuleMatcher
|
10
10
|
def initialize(rule, invert: false)
|
11
11
|
@rule = rule
|
12
12
|
@invert = invert
|
13
13
|
end
|
14
14
|
|
15
15
|
def call(exception:, action:)
|
16
|
-
|
16
|
+
result = matches?(exception:, action:)
|
17
|
+
@invert ? !result : result
|
17
18
|
rescue StandardError => e
|
18
19
|
Axn::Util.piping_error("determining if handler applies to exception", action:, exception: e)
|
19
20
|
end
|
@@ -79,6 +80,44 @@ module Action
|
|
79
80
|
false
|
80
81
|
end
|
81
82
|
end
|
83
|
+
|
84
|
+
class Matcher
|
85
|
+
# NOTE: invert means it's an unless rather than an if. This will apply to ALL rules (sufficient for current use case,
|
86
|
+
# but flagging if we ever extend this for complex matching)
|
87
|
+
def initialize(rules, invert: false)
|
88
|
+
@rules = Array(rules).compact
|
89
|
+
@invert = invert
|
90
|
+
end
|
91
|
+
|
92
|
+
def call(exception:, action:)
|
93
|
+
matches?(exception:, action:)
|
94
|
+
rescue StandardError => e
|
95
|
+
Axn::Util.piping_error("determining if handler applies to exception", action:, exception: e)
|
96
|
+
end
|
97
|
+
|
98
|
+
def static? = @rules.empty?
|
99
|
+
def invert? = !!@invert
|
100
|
+
|
101
|
+
# Class method to build matcher from kwargs
|
102
|
+
def self.build(if: nil, unless: nil)
|
103
|
+
if_condition = binding.local_variable_get(:if)
|
104
|
+
unless_condition = binding.local_variable_get(:unless)
|
105
|
+
|
106
|
+
raise Action::UnsupportedArgument, "providing both :if and :unless" if if_condition && unless_condition
|
107
|
+
|
108
|
+
new(Array(if_condition || unless_condition).compact, invert: !!unless_condition)
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def matches?(exception:, action:)
|
114
|
+
return true if @rules.empty?
|
115
|
+
|
116
|
+
@rules.all? do |rule|
|
117
|
+
SingleRuleMatcher.new(rule, invert: @invert).call(exception:, action:)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
82
121
|
end
|
83
122
|
end
|
84
123
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Action
|
4
|
+
module Core
|
5
|
+
module Flow
|
6
|
+
module Handlers
|
7
|
+
module Resolvers
|
8
|
+
class BaseResolver
|
9
|
+
def initialize(registry, event_type, action:, exception:)
|
10
|
+
@registry = registry
|
11
|
+
@event_type = event_type
|
12
|
+
@action = action
|
13
|
+
@exception = exception
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
attr_reader :registry, :event_type, :action, :exception
|
19
|
+
|
20
|
+
def candidate_entries = registry.for(event_type)
|
21
|
+
def matching_entries = candidate_entries.select { |descriptor| descriptor.matches?(action:, exception:) }
|
22
|
+
def static_entries = candidate_entries.select(&:static?)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action/core/flow/handlers/invoker"
|
4
|
+
|
5
|
+
module Action
|
6
|
+
module Core
|
7
|
+
module Flow
|
8
|
+
module Handlers
|
9
|
+
module Resolvers
|
10
|
+
# Internal: resolves and executes callbacks
|
11
|
+
class CallbackResolver < BaseResolver
|
12
|
+
def execute_callbacks
|
13
|
+
matching_entries.each do |descriptor|
|
14
|
+
execute_callback(descriptor)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# Executes a specific callback descriptor
|
21
|
+
def execute_callback(descriptor)
|
22
|
+
Invoker.call(operation: "executing callback", action:, handler: descriptor.handler, exception:)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action/core/flow/handlers/invoker"
|
4
|
+
|
5
|
+
module Action
|
6
|
+
module Core
|
7
|
+
module Flow
|
8
|
+
module Handlers
|
9
|
+
module Resolvers
|
10
|
+
# Internal: resolves messages with different strategies
|
11
|
+
class MessageResolver < BaseResolver
|
12
|
+
DEFAULT_ERROR = "Something went wrong"
|
13
|
+
DEFAULT_SUCCESS = "Action completed successfully"
|
14
|
+
|
15
|
+
def resolve_message
|
16
|
+
descriptor = matching_entries.detect { |d| message_from(d) }
|
17
|
+
message_from(descriptor) || fallback_message
|
18
|
+
end
|
19
|
+
|
20
|
+
def resolve_default_message
|
21
|
+
message_from(default_descriptor) || fallback_message
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def default_descriptor
|
27
|
+
# NOTE: descriptor.handler check avoids returning a prefix-only descriptor (which
|
28
|
+
# needs to look up a default handler via this method to return a message)
|
29
|
+
static_entries.detect { |descriptor| descriptor.handler && message_from(descriptor) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def message_from(descriptor)
|
33
|
+
message = resolved_message_body(descriptor)
|
34
|
+
return nil unless message.present?
|
35
|
+
|
36
|
+
descriptor.prefix ? "#{descriptor.prefix}#{message}" : message
|
37
|
+
end
|
38
|
+
|
39
|
+
def resolved_message_body(descriptor)
|
40
|
+
return nil unless descriptor
|
41
|
+
|
42
|
+
if descriptor.handler
|
43
|
+
invoke_handler(descriptor.handler)
|
44
|
+
elsif exception
|
45
|
+
exception.message
|
46
|
+
elsif descriptor.prefix
|
47
|
+
# For prefix-only success messages, find a default message from other descriptors
|
48
|
+
invoke_handler(default_descriptor&.handler)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def invoke_handler(handler) = handler ? Invoker.call(operation: "determining message callable", action:, handler:, exception:).presence : nil
|
53
|
+
def fallback_message = event_type == :success ? DEFAULT_SUCCESS : DEFAULT_ERROR
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -9,9 +9,12 @@ module Action
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
+
require "action/core/flow/handlers/base_descriptor"
|
13
|
+
require "action/core/flow/handlers/matcher"
|
14
|
+
require "action/core/flow/handlers/resolvers/base_resolver"
|
15
|
+
require "action/core/flow/handlers/descriptors/message_descriptor"
|
16
|
+
require "action/core/flow/handlers/descriptors/callback_descriptor"
|
12
17
|
require "action/core/flow/handlers/invoker"
|
18
|
+
require "action/core/flow/handlers/resolvers/callback_resolver"
|
13
19
|
require "action/core/flow/handlers/registry"
|
14
|
-
require "action/core/flow/handlers/
|
15
|
-
require "action/core/flow/handlers/base_handler"
|
16
|
-
require "action/core/flow/handlers/callback_handler"
|
17
|
-
require "action/core/flow/handlers/message_handler"
|
20
|
+
require "action/core/flow/handlers/resolvers/message_resolver"
|
@@ -11,64 +11,38 @@ module Action
|
|
11
11
|
class_attribute :_messages_registry, default: Action::Core::Flow::Handlers::Registry.empty
|
12
12
|
|
13
13
|
extend ClassMethods
|
14
|
-
include InstanceMethods
|
15
14
|
end
|
16
15
|
end
|
17
16
|
|
18
17
|
module ClassMethods
|
19
|
-
# Internal: resolve a message for the given event (conditional first, then static)
|
20
|
-
def _message_for(event_type, action:, exception: nil)
|
21
|
-
_conditional_message_for(event_type, action:, exception:) ||
|
22
|
-
_static_message_for(event_type, action:, exception:)
|
23
|
-
end
|
24
|
-
|
25
|
-
def _conditional_message_for(event_type, action:, exception: nil)
|
26
|
-
_messages_registry.for(event_type).each do |handler|
|
27
|
-
next if handler.respond_to?(:static?) && handler.static?
|
28
|
-
|
29
|
-
msg = handler.apply(action:, exception:)
|
30
|
-
return msg if msg.present?
|
31
|
-
end
|
32
|
-
nil
|
33
|
-
end
|
34
|
-
|
35
|
-
def _static_message_for(event_type, action:, exception: nil)
|
36
|
-
_messages_registry.for(event_type).each do |handler|
|
37
|
-
next unless handler.respond_to?(:static?) && handler.static?
|
38
|
-
|
39
|
-
msg = handler.apply(action:, exception:)
|
40
|
-
return msg if msg.present?
|
41
|
-
end
|
42
|
-
nil
|
43
|
-
end
|
44
|
-
|
45
18
|
def success(message = nil, **, &) = _add_message(:success, message:, **, &)
|
46
19
|
def error(message = nil, **, &) = _add_message(:error, message:, **, &)
|
47
20
|
|
48
|
-
def default_error = new.internal_context.default_error
|
49
|
-
def default_success = new.internal_context.default_success
|
50
|
-
|
51
21
|
private
|
52
22
|
|
53
23
|
def _add_message(kind, message:, **kwargs, &block)
|
54
|
-
raise
|
55
|
-
|
56
|
-
condition = kwargs.key?(:if) ? kwargs[:if] : kwargs[:unless]
|
24
|
+
raise Action::UnsupportedArgument, "calling #{kind} with both :if and :unless" if kwargs.key?(:if) && kwargs.key?(:unless)
|
25
|
+
raise Action::UnsupportedArgument, "Combining from: with if: or unless:" if kwargs.key?(:from) && (kwargs.key?(:if) || kwargs.key?(:unless))
|
57
26
|
raise ArgumentError, "Provide either a message or a block, not both" if message && block_given?
|
58
|
-
raise ArgumentError, "Provide a message or
|
27
|
+
raise ArgumentError, "Provide a message, block, or prefix" unless message || block_given? || kwargs[:prefix]
|
28
|
+
raise ArgumentError, "from: only applies to error messages" if kwargs.key?(:from) && kind != :error
|
59
29
|
|
60
|
-
|
30
|
+
# If message is already a descriptor, use it directly
|
31
|
+
entry = if message.is_a?(Action::Core::Flow::Handlers::Descriptors::MessageDescriptor)
|
32
|
+
raise ArgumentError, "Cannot pass additional configuration with prebuilt descriptor" if kwargs.any? || block_given?
|
33
|
+
|
34
|
+
message
|
35
|
+
else
|
36
|
+
Action::Core::Flow::Handlers::Descriptors::MessageDescriptor.build(
|
37
|
+
handler: block_given? ? block : message,
|
38
|
+
**kwargs,
|
39
|
+
)
|
40
|
+
end
|
61
41
|
|
62
|
-
matcher = condition.nil? ? nil : Action::Core::Flow::Handlers::Matcher.new(condition, invert: kwargs.key?(:unless))
|
63
|
-
entry = Action::Core::Flow::Handlers::MessageHandler.new(matcher:, handler:)
|
64
42
|
self._messages_registry = _messages_registry.register(event_type: kind, entry:)
|
65
43
|
true
|
66
44
|
end
|
67
45
|
end
|
68
|
-
|
69
|
-
module InstanceMethods
|
70
|
-
delegate :default_error, :default_success, to: :internal_context
|
71
|
-
end
|
72
46
|
end
|
73
47
|
end
|
74
48
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Action
|
4
|
+
module Core
|
5
|
+
module NestingTracking
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
extend ClassMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def _nested_in_another_axn?
|
14
|
+
NestingTracking._current_axn_stack.any?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def _tracking_nesting(axn)
|
19
|
+
NestingTracking._current_axn_stack.push(axn)
|
20
|
+
yield
|
21
|
+
ensure
|
22
|
+
NestingTracking._current_axn_stack.pop
|
23
|
+
end
|
24
|
+
|
25
|
+
# Shared method for both class and instance access
|
26
|
+
def self._current_axn_stack
|
27
|
+
ActiveSupport::IsolatedExecutionState[:_axn_stack] ||= []
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|