bcdd-result 0.13.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -4
- data/CHANGELOG.md +61 -21
- data/README.md +397 -227
- data/Rakefile +1 -1
- data/Steepfile +1 -1
- data/examples/multiple_listeners/Rakefile +8 -8
- data/examples/multiple_listeners/app/models/account/owner_creation.rb +1 -1
- data/examples/multiple_listeners/app/models/user/creation.rb +1 -1
- data/examples/multiple_listeners/app/models/user/token/creation.rb +1 -1
- data/examples/multiple_listeners/config/initializers/bcdd.rb +0 -2
- data/examples/multiple_listeners/config.rb +3 -3
- data/examples/multiple_listeners/db/setup.rb +2 -3
- data/examples/multiple_listeners/lib/bcdd/result/event_logs_record.rb +27 -0
- data/examples/multiple_listeners/lib/event_logs_listener/stdout.rb +60 -0
- data/examples/multiple_listeners/lib/runtime_breaker.rb +1 -1
- data/examples/service_objects/Rakefile +36 -0
- data/examples/service_objects/app/models/account/member.rb +10 -0
- data/examples/service_objects/app/models/account.rb +11 -0
- data/examples/service_objects/app/models/user/token.rb +7 -0
- data/examples/service_objects/app/models/user.rb +15 -0
- data/examples/service_objects/app/services/account/owner_creation.rb +47 -0
- data/examples/service_objects/app/services/application_service.rb +79 -0
- data/examples/service_objects/app/services/user/creation.rb +56 -0
- data/examples/service_objects/app/services/user/token/creation.rb +37 -0
- data/examples/service_objects/config/boot.rb +17 -0
- data/examples/service_objects/config/initializers/bcdd.rb +9 -0
- data/examples/service_objects/config.rb +20 -0
- data/examples/service_objects/db/setup.rb +49 -0
- data/examples/single_listener/Rakefile +5 -5
- data/examples/single_listener/app/models/account/owner_creation.rb +1 -1
- data/examples/single_listener/app/models/user/creation.rb +1 -1
- data/examples/single_listener/app/models/user/token/creation.rb +1 -1
- data/examples/single_listener/config/initializers/bcdd.rb +0 -2
- data/examples/single_listener/config.rb +1 -1
- data/examples/single_listener/lib/{single_transitions_listener.rb → single_event_logs_listener.rb} +32 -23
- data/lib/bcdd/{result/context → context}/callable_and_then.rb +6 -5
- data/lib/bcdd/{result/context → context}/expectations/mixin.rb +1 -1
- data/lib/bcdd/{result/context → context}/expectations.rb +2 -2
- data/lib/bcdd/context/failure.rb +9 -0
- data/lib/bcdd/{result/context → context}/mixin.rb +2 -2
- data/lib/bcdd/{result/context → context}/success.rb +11 -11
- data/lib/bcdd/context.rb +115 -0
- data/lib/bcdd/failure.rb +23 -0
- data/lib/bcdd/result/_self.rb +198 -0
- data/lib/bcdd/result/callable_and_then/caller.rb +1 -1
- data/lib/bcdd/result/config/switchers/addons.rb +2 -2
- data/lib/bcdd/result/config/switchers/constant_aliases.rb +1 -3
- data/lib/bcdd/result/config/switchers/features.rb +5 -5
- data/lib/bcdd/result/config/switchers/pattern_matching.rb +1 -1
- data/lib/bcdd/result/config.rb +7 -5
- data/lib/bcdd/result/contract/type_checker.rb +4 -0
- data/lib/bcdd/result/{transitions → event_logs}/config.rb +5 -3
- data/lib/bcdd/result/{transitions → event_logs}/listener.rb +5 -5
- data/lib/bcdd/result/{transitions → event_logs}/listeners.rb +17 -17
- data/lib/bcdd/result/{transitions → event_logs}/tracking/disabled.rb +1 -1
- data/lib/bcdd/result/{transitions → event_logs}/tracking/enabled.rb +15 -13
- data/lib/bcdd/result/{transitions → event_logs}/tracking.rb +4 -3
- data/lib/bcdd/result/{transitions → event_logs}/tree.rb +27 -11
- data/lib/bcdd/result/event_logs.rb +27 -0
- data/lib/bcdd/result/failure.rb +1 -3
- data/lib/bcdd/result/success.rb +1 -3
- data/lib/bcdd/result/version.rb +1 -1
- data/lib/bcdd/result.rb +23 -191
- data/lib/bcdd/success.rb +23 -0
- data/sig/bcdd/context.rbs +175 -0
- data/sig/bcdd/failure.rbs +13 -0
- data/sig/bcdd/result/config.rbs +1 -3
- data/sig/bcdd/result/context.rbs +2 -174
- data/sig/bcdd/result/contract.rbs +1 -0
- data/sig/bcdd/result/{transitions.rbs → event_logs.rbs} +19 -19
- data/sig/bcdd/result.rbs +13 -31
- data/sig/bcdd/success.rbs +13 -0
- metadata +41 -24
- data/examples/multiple_listeners/lib/bcdd/result/transitions_record.rb +0 -28
- data/examples/multiple_listeners/lib/transitions_listener/stdout.rb +0 -54
- data/lib/bcdd/result/context/failure.rb +0 -9
- data/lib/bcdd/result/context.rb +0 -93
- data/lib/bcdd/result/failure/methods.rb +0 -21
- data/lib/bcdd/result/success/methods.rb +0 -21
- data/lib/bcdd/result/transitions.rb +0 -27
@@ -10,7 +10,7 @@ require_relative 'config/initializers/bcdd'
|
|
10
10
|
require 'db/setup'
|
11
11
|
|
12
12
|
require 'lib/bcdd/result/rollback_on_failure'
|
13
|
-
require 'lib/
|
13
|
+
require 'lib/single_event_logs_listener'
|
14
14
|
require 'lib/runtime_breaker'
|
15
15
|
|
16
16
|
require 'app/models/account'
|
data/examples/single_listener/lib/{single_transitions_listener.rb → single_event_logs_listener.rb}
RENAMED
@@ -1,15 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class
|
4
|
-
include BCDD::Result::
|
3
|
+
class SingleEventLogsListener
|
4
|
+
include BCDD::Result::EventLogs::Listener
|
5
5
|
|
6
|
-
# A listener will be initialized before the first
|
6
|
+
# A listener will be initialized before the first event log, and it is discarded after the last one.
|
7
7
|
def initialize
|
8
8
|
@buffer = []
|
9
9
|
end
|
10
10
|
|
11
|
-
# This method will be called before each
|
12
|
-
# The parent
|
11
|
+
# This method will be called before each event log block.
|
12
|
+
# The parent event log block will be called first in the case of nested blocks.
|
13
13
|
#
|
14
14
|
# @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
|
15
15
|
def on_start(scope:)
|
@@ -18,11 +18,11 @@ class SingleTransitionsListener
|
|
18
18
|
@buffer << [id, "##{id} #{name} - #{desc}".chomp('- ')]
|
19
19
|
end
|
20
20
|
|
21
|
-
# This method will wrap all the
|
22
|
-
# It can be used to perform an instrumentation (measure/report) of the
|
21
|
+
# This method will wrap all the event_logs in the same block.
|
22
|
+
# It can be used to perform an instrumentation (measure/report) of the event_logs.
|
23
23
|
#
|
24
24
|
# @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
|
25
|
-
def
|
25
|
+
def around_event_logs(scope:)
|
26
26
|
yield
|
27
27
|
end
|
28
28
|
|
@@ -56,43 +56,46 @@ class SingleTransitionsListener
|
|
56
56
|
@buffer << [id, " * #{kind}(#{type}) from method: #{method_name}".chomp('from method: ')]
|
57
57
|
end
|
58
58
|
|
59
|
-
MapNestedMessages = ->(
|
60
|
-
|
59
|
+
MapNestedMessages = ->(event_logs, buffer, hide_given_and_continue) do
|
60
|
+
ids_level_parent = event_logs.dig(:metadata, :ids, :level_parent)
|
61
61
|
|
62
|
-
messages = buffer.filter_map { |(id, msg)| "#{' ' *
|
62
|
+
messages = buffer.filter_map { |(id, msg)| "#{' ' * ids_level_parent[id].first}#{msg}" if ids_level_parent[id] }
|
63
63
|
|
64
64
|
messages.reject! { _1.match?(/\(_(given|continue)_\)/) } if hide_given_and_continue
|
65
65
|
|
66
66
|
messages
|
67
67
|
end
|
68
68
|
|
69
|
-
# This method will be called at the end of the
|
69
|
+
# This method will be called at the end of the event_logs tracking.
|
70
70
|
#
|
71
|
-
# @param
|
71
|
+
# @param event_logs:
|
72
72
|
# {
|
73
73
|
# :version => 1,
|
74
74
|
# :metadata => {
|
75
75
|
# :duration => 0,
|
76
76
|
# :trace_id => nil,
|
77
|
-
# :
|
78
|
-
#
|
77
|
+
# :ids => {
|
78
|
+
# :tree => [0, [[1, []], [2, []]]],
|
79
|
+
# :matrix => { 0 => [0, 0], 1 => [1, 1], 2 => [2, 1]},
|
80
|
+
# :level_parent => { 0 => [0, 0], 1 => [1, 0], 2 => [1, 0]}
|
81
|
+
# }
|
79
82
|
# },
|
80
83
|
# :records => [
|
81
84
|
# # ...
|
82
85
|
# ]
|
83
86
|
# }
|
84
|
-
def on_finish(
|
85
|
-
messages = MapNestedMessages[
|
87
|
+
def on_finish(event_logs:)
|
88
|
+
messages = MapNestedMessages[event_logs, @buffer, ENV['HIDE_GIVEN_AND_CONTINUE']]
|
86
89
|
|
87
90
|
puts messages.join("\n")
|
88
91
|
end
|
89
92
|
|
90
|
-
# This method will be called when an exception is raised during the
|
93
|
+
# This method will be called when an exception is raised during the event_logs tracking.
|
91
94
|
#
|
92
95
|
# @param exception: Exception
|
93
|
-
# @param
|
94
|
-
def before_interruption(exception:,
|
95
|
-
messages = MapNestedMessages[
|
96
|
+
# @param event_logs: Hash
|
97
|
+
def before_interruption(exception:, event_logs:)
|
98
|
+
messages = MapNestedMessages[event_logs, @buffer, ENV['HIDE_GIVEN_AND_CONTINUE']]
|
96
99
|
|
97
100
|
puts messages.join("\n")
|
98
101
|
|
@@ -101,8 +104,14 @@ class SingleTransitionsListener
|
|
101
104
|
bc.add_silencer { |line| /lib\/bcdd\/result/.match?(line) }
|
102
105
|
bc.add_silencer { |line| line.include?(RUBY_VERSION) }
|
103
106
|
|
104
|
-
|
107
|
+
dir = "#{FileUtils.pwd[1..]}/"
|
105
108
|
|
106
|
-
|
109
|
+
listener_filename = File.basename(__FILE__).chomp('.rb')
|
110
|
+
|
111
|
+
cb = bc.clean(exception.backtrace)
|
112
|
+
cb.each { _1.sub!(dir, '') }
|
113
|
+
cb.reject! { _1.match?(/block \(\d levels?\) in|in `block in|internal:kernel|#{listener_filename}/) }
|
114
|
+
|
115
|
+
puts "\nException:\n #{exception.message} (#{exception.class})\n\nBacktrace:\n #{cb.join("\n ")}"
|
107
116
|
end
|
108
117
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
module BCDD
|
4
4
|
module Context::CallableAndThen
|
5
|
-
class Caller < CallableAndThen::Caller
|
5
|
+
class Caller < Result::CallableAndThen::Caller
|
6
6
|
module KeyArgs
|
7
7
|
def self.parameters?(source)
|
8
8
|
parameters = source.parameters.map(&:first)
|
@@ -11,7 +11,7 @@ class BCDD::Result
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.invalid_arity(source, method)
|
14
|
-
CallableAndThen::Error::InvalidArity.build(source: source, method: method, arity: 'only keyword args')
|
14
|
+
Result::CallableAndThen::Error::InvalidArity.build(source: source, method: method, arity: 'only keyword args')
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
@@ -28,9 +28,10 @@ class BCDD::Result
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def self.ensure_result_object(source, value, result)
|
31
|
-
return result.tap { result.send(:
|
31
|
+
return result.tap { result.send(:memo).then { _1.merge!(value.merge(_1)) } } if result.is_a?(Context)
|
32
32
|
|
33
|
-
raise Error::UnexpectedOutcome.build(outcome: result, origin: source,
|
33
|
+
raise Result::Error::UnexpectedOutcome.build(outcome: result, origin: source,
|
34
|
+
expected: Context::EXPECTED_OUTCOME)
|
34
35
|
end
|
35
36
|
|
36
37
|
private_class_method :call_proc!, :call_method!
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class BCDD::
|
3
|
+
class BCDD::Context
|
4
4
|
class Expectations < BCDD::Result::Expectations
|
5
5
|
require_relative 'expectations/mixin'
|
6
6
|
|
@@ -9,7 +9,7 @@ class BCDD::Result::Context
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.result_factory_without_expectations
|
12
|
-
::BCDD::
|
12
|
+
::BCDD::Context
|
13
13
|
end
|
14
14
|
|
15
15
|
private_class_method :mixin!, :mixin_module, :result_factory_without_expectations
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class BCDD::
|
3
|
+
class BCDD::Context
|
4
4
|
module Mixin
|
5
5
|
Factory = BCDD::Result::Mixin::Factory
|
6
6
|
|
@@ -50,7 +50,7 @@ class BCDD::Result::Context
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def self.result_factory
|
53
|
-
::BCDD::
|
53
|
+
::BCDD::Context
|
54
54
|
end
|
55
55
|
|
56
56
|
private_class_method :mixin_module, :result_factory
|
@@ -1,21 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class BCDD::
|
4
|
-
class
|
3
|
+
class BCDD::Context
|
4
|
+
class Error < BCDD::Result::Error
|
5
5
|
InvalidExposure = ::Class.new(self)
|
6
6
|
end
|
7
7
|
|
8
|
-
class
|
9
|
-
include ::BCDD::
|
8
|
+
class Success < self
|
9
|
+
include ::BCDD::Success
|
10
10
|
|
11
|
-
FetchValues = ->(
|
12
|
-
fetched_values =
|
11
|
+
FetchValues = ->(memo_values, keys) do
|
12
|
+
fetched_values = memo_values.fetch_values(*keys)
|
13
13
|
|
14
14
|
keys.zip(fetched_values).to_h
|
15
15
|
rescue ::KeyError => e
|
16
|
-
message = "#{e.message}. Available to expose: #{
|
16
|
+
message = "#{e.message}. Available to expose: #{memo_values.keys.map(&:inspect).join(', ')}"
|
17
17
|
|
18
|
-
raise
|
18
|
+
raise Error::InvalidExposure, message
|
19
19
|
end
|
20
20
|
|
21
21
|
def and_expose(type, keys, terminal: true)
|
@@ -23,11 +23,11 @@ class BCDD::Result
|
|
23
23
|
raise ::ArgumentError, 'keys must be an Array of Symbols'
|
24
24
|
end
|
25
25
|
|
26
|
-
|
26
|
+
EventLogs.tracking.reset_and_then!
|
27
27
|
|
28
|
-
|
28
|
+
memo_values = memo.merge(value)
|
29
29
|
|
30
|
-
value_to_expose = FetchValues.call(
|
30
|
+
value_to_expose = FetchValues.call(memo_values, keys)
|
31
31
|
|
32
32
|
expectations = type_checker.expectations
|
33
33
|
|
data/lib/bcdd/context.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Context < BCDD::Result
|
4
|
+
require_relative 'context/failure'
|
5
|
+
require_relative 'context/success'
|
6
|
+
require_relative 'context/mixin'
|
7
|
+
require_relative 'context/expectations'
|
8
|
+
require_relative 'context/callable_and_then'
|
9
|
+
|
10
|
+
EXPECTED_OUTCOME = 'BCDD::Context::Success or BCDD::Context::Failure'
|
11
|
+
|
12
|
+
def self.Success(type, **value)
|
13
|
+
Success.new(type: type, value: value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.Failure(type, **value)
|
17
|
+
Failure.new(type: type, value: value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(type:, value:, source: nil, expectations: nil, terminal: nil)
|
21
|
+
value.is_a?(::Hash) or raise ::ArgumentError, 'value must be a Hash'
|
22
|
+
|
23
|
+
@memo = {}
|
24
|
+
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def and_then(method_name = nil, **injected_value, &block)
|
29
|
+
super(method_name, injected_value, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def and_then!(source, **injected_value)
|
33
|
+
_call = injected_value.delete(:_call)
|
34
|
+
|
35
|
+
memo.merge!(injected_value)
|
36
|
+
|
37
|
+
super(source, injected_value, _call: _call)
|
38
|
+
end
|
39
|
+
|
40
|
+
def [](key)
|
41
|
+
value[key]
|
42
|
+
end
|
43
|
+
|
44
|
+
def dig(...)
|
45
|
+
value.dig(...)
|
46
|
+
end
|
47
|
+
|
48
|
+
def fetch(...)
|
49
|
+
value.fetch(...)
|
50
|
+
end
|
51
|
+
|
52
|
+
def slice(...)
|
53
|
+
value.slice(...)
|
54
|
+
end
|
55
|
+
|
56
|
+
def values_at(...)
|
57
|
+
value.values_at(...)
|
58
|
+
end
|
59
|
+
|
60
|
+
def fetch_values(...)
|
61
|
+
value.fetch_values(...)
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
attr_reader :memo
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
SourceMethodArity = ->(method) do
|
71
|
+
return 0 if method.arity.zero?
|
72
|
+
|
73
|
+
parameters = method.parameters.map(&:first)
|
74
|
+
|
75
|
+
return 1 if !parameters.empty? && parameters.all?(/\Akey/)
|
76
|
+
|
77
|
+
-1
|
78
|
+
end
|
79
|
+
|
80
|
+
def call_and_then_source_method!(method, injected_value)
|
81
|
+
memo.merge!(value.merge(injected_value))
|
82
|
+
|
83
|
+
case SourceMethodArity[method]
|
84
|
+
when 0 then source.send(method.name)
|
85
|
+
when 1 then source.send(method.name, **memo)
|
86
|
+
else raise Error::InvalidSourceMethodArity.build(source: source, method: method, max_arity: 1)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def call_and_then_block!(block)
|
91
|
+
memo.merge!(value)
|
92
|
+
|
93
|
+
block.call(memo)
|
94
|
+
end
|
95
|
+
|
96
|
+
def call_and_then_callable!(source, value:, injected_value:, method_name:)
|
97
|
+
memo.merge!(value.merge(injected_value))
|
98
|
+
|
99
|
+
CallableAndThen::Caller.call(source, value: memo, injected_value: injected_value, method_name: method_name)
|
100
|
+
end
|
101
|
+
|
102
|
+
def ensure_result_object(result, origin:)
|
103
|
+
raise_unexpected_outcome_error(result, origin) unless result.is_a?(BCDD::Context)
|
104
|
+
|
105
|
+
return result.tap { _1.memo.merge!(memo) } if result.source.equal?(source)
|
106
|
+
|
107
|
+
raise Error::InvalidResultSource.build(given_result: result, expected_source: source)
|
108
|
+
end
|
109
|
+
|
110
|
+
def raise_unexpected_outcome_error(result, origin)
|
111
|
+
raise Error::UnexpectedOutcome.build(outcome: result, origin: origin, expected: EXPECTED_OUTCOME)
|
112
|
+
end
|
113
|
+
|
114
|
+
private_constant :SourceMethodArity
|
115
|
+
end
|
data/lib/bcdd/failure.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BCDD
|
4
|
+
module Failure
|
5
|
+
def success?(_type = nil)
|
6
|
+
false
|
7
|
+
end
|
8
|
+
|
9
|
+
def failure?(type = nil)
|
10
|
+
type.nil? || type_checker.allow_failure?([type])
|
11
|
+
end
|
12
|
+
|
13
|
+
def value_or
|
14
|
+
yield(value)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def kind
|
20
|
+
:failure
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result
|
4
|
+
attr_accessor :unknown, :event_logs
|
5
|
+
|
6
|
+
attr_reader :source, :data, :type_checker, :terminal
|
7
|
+
|
8
|
+
protected :source
|
9
|
+
|
10
|
+
private :unknown, :unknown=, :type_checker, :event_logs=
|
11
|
+
|
12
|
+
def self.config
|
13
|
+
Config.instance
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.configuration(freeze: true)
|
17
|
+
yield(config)
|
18
|
+
|
19
|
+
freeze and config.freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(type:, value:, source: nil, expectations: nil, terminal: nil)
|
23
|
+
data = Data.new(kind, type, value)
|
24
|
+
|
25
|
+
@type_checker = Contract.evaluate(data, expectations)
|
26
|
+
@source = source
|
27
|
+
@terminal = kind == :failure || (terminal && !IgnoredTypes.include?(type))
|
28
|
+
@data = data
|
29
|
+
|
30
|
+
self.unknown = true
|
31
|
+
self.event_logs = EventLogs::Tracking::EMPTY
|
32
|
+
|
33
|
+
EventLogs.tracking.record(self)
|
34
|
+
end
|
35
|
+
|
36
|
+
def terminal?
|
37
|
+
terminal
|
38
|
+
end
|
39
|
+
|
40
|
+
def type
|
41
|
+
data.type
|
42
|
+
end
|
43
|
+
|
44
|
+
def value
|
45
|
+
data.value
|
46
|
+
end
|
47
|
+
|
48
|
+
def type?(arg)
|
49
|
+
type_checker.allow!(arg.to_sym) == type
|
50
|
+
end
|
51
|
+
|
52
|
+
def success?(_type = nil)
|
53
|
+
raise Error::NotImplemented
|
54
|
+
end
|
55
|
+
|
56
|
+
def failure?(_type = nil)
|
57
|
+
raise Error::NotImplemented
|
58
|
+
end
|
59
|
+
|
60
|
+
def value_or(&_block)
|
61
|
+
raise Error::NotImplemented
|
62
|
+
end
|
63
|
+
|
64
|
+
def on(*types, &block)
|
65
|
+
raise Error::MissingTypeArgument if types.empty?
|
66
|
+
|
67
|
+
tap { known(block) if type_checker.allow?(types) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def on_success(*types, &block)
|
71
|
+
tap { known(block) if type_checker.allow_success?(types) && success? }
|
72
|
+
end
|
73
|
+
|
74
|
+
def on_failure(*types, &block)
|
75
|
+
tap { known(block) if type_checker.allow_failure?(types) && failure? }
|
76
|
+
end
|
77
|
+
|
78
|
+
def on_unknown
|
79
|
+
tap { yield(value, type) if unknown }
|
80
|
+
end
|
81
|
+
|
82
|
+
def and_then(method_name = nil, injected_value = nil, &block)
|
83
|
+
return self if terminal?
|
84
|
+
|
85
|
+
method_name && block and raise ::ArgumentError, 'method_name and block are mutually exclusive'
|
86
|
+
|
87
|
+
method_name ? call_and_then_source_method(method_name, injected_value) : call_and_then_block(block)
|
88
|
+
end
|
89
|
+
|
90
|
+
def and_then!(source, injected_value = nil, _call: nil)
|
91
|
+
raise Error::CallableAndThenDisabled unless Config.instance.feature.enabled?(:and_then!)
|
92
|
+
|
93
|
+
return self if terminal?
|
94
|
+
|
95
|
+
call_and_then_callable!(source, value: value, injected_value: injected_value, method_name: _call)
|
96
|
+
end
|
97
|
+
|
98
|
+
def handle
|
99
|
+
handler = Handler.new(self, type_checker: type_checker)
|
100
|
+
|
101
|
+
yield handler
|
102
|
+
|
103
|
+
handler.send(:outcome)
|
104
|
+
end
|
105
|
+
|
106
|
+
def ==(other)
|
107
|
+
self.class == other.class && type == other.type && value == other.value
|
108
|
+
end
|
109
|
+
|
110
|
+
def hash
|
111
|
+
[self.class, type, value].hash
|
112
|
+
end
|
113
|
+
|
114
|
+
def inspect
|
115
|
+
format('#<%<class_name>s type=%<type>p value=%<value>p>', class_name: self.class.name, type: type, value: value)
|
116
|
+
end
|
117
|
+
|
118
|
+
def deconstruct
|
119
|
+
[type, value]
|
120
|
+
end
|
121
|
+
|
122
|
+
TYPE_AND_VALUE = %i[type value].freeze
|
123
|
+
|
124
|
+
def deconstruct_keys(keys)
|
125
|
+
output = TYPE_AND_VALUE.each_with_object({}) do |key, hash|
|
126
|
+
hash[key] = send(key) if keys.include?(key)
|
127
|
+
end
|
128
|
+
|
129
|
+
output.empty? ? value : output
|
130
|
+
end
|
131
|
+
|
132
|
+
def method_missing(name, *args, &block)
|
133
|
+
name.end_with?('?') ? is?(name.to_s.chomp('?')) : super
|
134
|
+
end
|
135
|
+
|
136
|
+
def respond_to_missing?(name, include_private = false)
|
137
|
+
name.end_with?('?') || super
|
138
|
+
end
|
139
|
+
|
140
|
+
alias is? type?
|
141
|
+
alias eql? ==
|
142
|
+
alias on_type on
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def kind
|
147
|
+
:unknown
|
148
|
+
end
|
149
|
+
|
150
|
+
def known(block)
|
151
|
+
self.unknown = false
|
152
|
+
|
153
|
+
block.call(value, type)
|
154
|
+
end
|
155
|
+
|
156
|
+
def call_and_then_source_method(method_name, injected_value)
|
157
|
+
method = source.method(method_name)
|
158
|
+
|
159
|
+
EventLogs.tracking.record_and_then(method, injected_value) do
|
160
|
+
result = call_and_then_source_method!(method, injected_value)
|
161
|
+
|
162
|
+
ensure_result_object(result, origin: :method)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def call_and_then_source_method!(method, injected_value)
|
167
|
+
case method.arity
|
168
|
+
when 0 then source.send(method.name)
|
169
|
+
when 1 then source.send(method.name, value)
|
170
|
+
when 2 then source.send(method.name, value, injected_value)
|
171
|
+
else raise Error::InvalidSourceMethodArity.build(source: source, method: method, max_arity: 2)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def call_and_then_block(block)
|
176
|
+
EventLogs.tracking.record_and_then(:block, nil) do
|
177
|
+
result = call_and_then_block!(block)
|
178
|
+
|
179
|
+
ensure_result_object(result, origin: :block)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def call_and_then_block!(block)
|
184
|
+
block.call(value)
|
185
|
+
end
|
186
|
+
|
187
|
+
def call_and_then_callable!(source, value:, injected_value:, method_name:)
|
188
|
+
CallableAndThen::Caller.call(source, value: value, injected_value: injected_value, method_name: method_name)
|
189
|
+
end
|
190
|
+
|
191
|
+
def ensure_result_object(result, origin:)
|
192
|
+
raise Error::UnexpectedOutcome.build(outcome: result, origin: origin) unless result.is_a?(::BCDD::Result)
|
193
|
+
|
194
|
+
return result if result.source.equal?(source)
|
195
|
+
|
196
|
+
raise Error::InvalidResultSource.build(given_result: result, expected_source: source)
|
197
|
+
end
|
198
|
+
end
|
@@ -5,7 +5,7 @@ class BCDD::Result
|
|
5
5
|
def self.call(source, value:, injected_value:, method_name:)
|
6
6
|
method = callable_method(source, method_name)
|
7
7
|
|
8
|
-
|
8
|
+
EventLogs.tracking.record_and_then(method, injected_value) do
|
9
9
|
result =
|
10
10
|
if source.is_a?(::Proc)
|
11
11
|
call_proc!(source, value, injected_value)
|
@@ -5,9 +5,9 @@ class BCDD::Result
|
|
5
5
|
module Addons
|
6
6
|
AFFECTS = %w[
|
7
7
|
BCDD::Result.mixin
|
8
|
-
BCDD::
|
8
|
+
BCDD::Context.mixin
|
9
9
|
BCDD::Result::Expectations.mixin
|
10
|
-
BCDD::
|
10
|
+
BCDD::Context::Expectations.mixin
|
11
11
|
].freeze
|
12
12
|
|
13
13
|
OPTIONS = {
|
@@ -4,9 +4,7 @@ class BCDD::Result
|
|
4
4
|
class Config
|
5
5
|
module ConstantAliases
|
6
6
|
MAPPING = {
|
7
|
-
'Result' => { target: ::Object, name: :Result, value: ::BCDD::Result }
|
8
|
-
'Context' => { target: ::Object, name: :Context, value: ::BCDD::Result::Context },
|
9
|
-
'BCDD::Context' => { target: ::BCDD, name: :Context, value: ::BCDD::Result::Context }
|
7
|
+
'Result' => { target: ::Object, name: :Result, value: ::BCDD::Result }
|
10
8
|
}.transform_values!(&:freeze).freeze
|
11
9
|
|
12
10
|
OPTIONS = MAPPING.to_h do |option_name, mapping|
|