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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -4
  3. data/CHANGELOG.md +61 -21
  4. data/README.md +397 -227
  5. data/Rakefile +1 -1
  6. data/Steepfile +1 -1
  7. data/examples/multiple_listeners/Rakefile +8 -8
  8. data/examples/multiple_listeners/app/models/account/owner_creation.rb +1 -1
  9. data/examples/multiple_listeners/app/models/user/creation.rb +1 -1
  10. data/examples/multiple_listeners/app/models/user/token/creation.rb +1 -1
  11. data/examples/multiple_listeners/config/initializers/bcdd.rb +0 -2
  12. data/examples/multiple_listeners/config.rb +3 -3
  13. data/examples/multiple_listeners/db/setup.rb +2 -3
  14. data/examples/multiple_listeners/lib/bcdd/result/event_logs_record.rb +27 -0
  15. data/examples/multiple_listeners/lib/event_logs_listener/stdout.rb +60 -0
  16. data/examples/multiple_listeners/lib/runtime_breaker.rb +1 -1
  17. data/examples/service_objects/Rakefile +36 -0
  18. data/examples/service_objects/app/models/account/member.rb +10 -0
  19. data/examples/service_objects/app/models/account.rb +11 -0
  20. data/examples/service_objects/app/models/user/token.rb +7 -0
  21. data/examples/service_objects/app/models/user.rb +15 -0
  22. data/examples/service_objects/app/services/account/owner_creation.rb +47 -0
  23. data/examples/service_objects/app/services/application_service.rb +79 -0
  24. data/examples/service_objects/app/services/user/creation.rb +56 -0
  25. data/examples/service_objects/app/services/user/token/creation.rb +37 -0
  26. data/examples/service_objects/config/boot.rb +17 -0
  27. data/examples/service_objects/config/initializers/bcdd.rb +9 -0
  28. data/examples/service_objects/config.rb +20 -0
  29. data/examples/service_objects/db/setup.rb +49 -0
  30. data/examples/single_listener/Rakefile +5 -5
  31. data/examples/single_listener/app/models/account/owner_creation.rb +1 -1
  32. data/examples/single_listener/app/models/user/creation.rb +1 -1
  33. data/examples/single_listener/app/models/user/token/creation.rb +1 -1
  34. data/examples/single_listener/config/initializers/bcdd.rb +0 -2
  35. data/examples/single_listener/config.rb +1 -1
  36. data/examples/single_listener/lib/{single_transitions_listener.rb → single_event_logs_listener.rb} +32 -23
  37. data/lib/bcdd/{result/context → context}/callable_and_then.rb +6 -5
  38. data/lib/bcdd/{result/context → context}/expectations/mixin.rb +1 -1
  39. data/lib/bcdd/{result/context → context}/expectations.rb +2 -2
  40. data/lib/bcdd/context/failure.rb +9 -0
  41. data/lib/bcdd/{result/context → context}/mixin.rb +2 -2
  42. data/lib/bcdd/{result/context → context}/success.rb +11 -11
  43. data/lib/bcdd/context.rb +115 -0
  44. data/lib/bcdd/failure.rb +23 -0
  45. data/lib/bcdd/result/_self.rb +198 -0
  46. data/lib/bcdd/result/callable_and_then/caller.rb +1 -1
  47. data/lib/bcdd/result/config/switchers/addons.rb +2 -2
  48. data/lib/bcdd/result/config/switchers/constant_aliases.rb +1 -3
  49. data/lib/bcdd/result/config/switchers/features.rb +5 -5
  50. data/lib/bcdd/result/config/switchers/pattern_matching.rb +1 -1
  51. data/lib/bcdd/result/config.rb +7 -5
  52. data/lib/bcdd/result/contract/type_checker.rb +4 -0
  53. data/lib/bcdd/result/{transitions → event_logs}/config.rb +5 -3
  54. data/lib/bcdd/result/{transitions → event_logs}/listener.rb +5 -5
  55. data/lib/bcdd/result/{transitions → event_logs}/listeners.rb +17 -17
  56. data/lib/bcdd/result/{transitions → event_logs}/tracking/disabled.rb +1 -1
  57. data/lib/bcdd/result/{transitions → event_logs}/tracking/enabled.rb +15 -13
  58. data/lib/bcdd/result/{transitions → event_logs}/tracking.rb +4 -3
  59. data/lib/bcdd/result/{transitions → event_logs}/tree.rb +27 -11
  60. data/lib/bcdd/result/event_logs.rb +27 -0
  61. data/lib/bcdd/result/failure.rb +1 -3
  62. data/lib/bcdd/result/success.rb +1 -3
  63. data/lib/bcdd/result/version.rb +1 -1
  64. data/lib/bcdd/result.rb +23 -191
  65. data/lib/bcdd/success.rb +23 -0
  66. data/sig/bcdd/context.rbs +175 -0
  67. data/sig/bcdd/failure.rbs +13 -0
  68. data/sig/bcdd/result/config.rbs +1 -3
  69. data/sig/bcdd/result/context.rbs +2 -174
  70. data/sig/bcdd/result/contract.rbs +1 -0
  71. data/sig/bcdd/result/{transitions.rbs → event_logs.rbs} +19 -19
  72. data/sig/bcdd/result.rbs +13 -31
  73. data/sig/bcdd/success.rbs +13 -0
  74. metadata +41 -24
  75. data/examples/multiple_listeners/lib/bcdd/result/transitions_record.rb +0 -28
  76. data/examples/multiple_listeners/lib/transitions_listener/stdout.rb +0 -54
  77. data/lib/bcdd/result/context/failure.rb +0 -9
  78. data/lib/bcdd/result/context.rb +0 -93
  79. data/lib/bcdd/result/failure/methods.rb +0 -21
  80. data/lib/bcdd/result/success/methods.rb +0 -21
  81. data/lib/bcdd/result/transitions.rb +0 -27
@@ -6,7 +6,7 @@ class Account
6
6
  include BCDD::Result::RollbackOnFailure
7
7
 
8
8
  def call(**input)
9
- BCDD::Result.transitions(name: self.class.name) do
9
+ BCDD::Result.event_logs(name: self.class.name) do
10
10
  Given(input)
11
11
  .and_then(:normalize_input)
12
12
  .and_then(:validate_input)
@@ -6,7 +6,7 @@ class User
6
6
  include BCDD::Result::RollbackOnFailure
7
7
 
8
8
  def call(**input)
9
- BCDD::Result.transitions(name: self.class.name) do
9
+ BCDD::Result.event_logs(name: self.class.name) do
10
10
  Given(input)
11
11
  .and_then(:normalize_input)
12
12
  .and_then(:validate_input)
@@ -5,7 +5,7 @@ class User::Token
5
5
  include BCDD::Context.mixin
6
6
 
7
7
  def call(**input)
8
- BCDD::Result.transitions(name: self.class.name) do
8
+ BCDD::Result.event_logs(name: self.class.name) do
9
9
  Given(input)
10
10
  .and_then(:normalize_input)
11
11
  .and_then(:validate_input)
@@ -3,8 +3,6 @@
3
3
  BCDD::Result.config.then do |config|
4
4
  config.addon.enable!(:continue)
5
5
 
6
- config.constant_alias.enable!('BCDD::Context')
7
-
8
6
  config.pattern_matching.disable!(:nil_as_valid_value_checking)
9
7
 
10
8
  # config.feature.disable!(:expectations) if Rails.env.production?
@@ -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/single_transitions_listener'
13
+ require 'lib/single_event_logs_listener'
14
14
  require 'lib/runtime_breaker'
15
15
 
16
16
  require 'app/models/account'
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class SingleTransitionsListener
4
- include BCDD::Result::Transitions::Listener
3
+ class SingleEventLogsListener
4
+ include BCDD::Result::EventLogs::Listener
5
5
 
6
- # A listener will be initialized before the first transition, and it is discarded after the last one.
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 transition block.
12
- # The parent transition block will be called first in the case of nested transition blocks.
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 transitions in the same block.
22
- # It can be used to perform an instrumentation (measure/report) of the transitions.
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 around_transitions(scope:)
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 = ->(transitions, buffer, hide_given_and_continue) do
60
- ids_matrix = transitions.dig(:metadata, :ids_matrix)
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)| "#{' ' * ids_matrix[id].last}#{msg}" if ids_matrix[id] }
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 transitions tracking.
69
+ # This method will be called at the end of the event_logs tracking.
70
70
  #
71
- # @param transitions:
71
+ # @param event_logs:
72
72
  # {
73
73
  # :version => 1,
74
74
  # :metadata => {
75
75
  # :duration => 0,
76
76
  # :trace_id => nil,
77
- # :ids_tree => [0, [[1, []], [2, []]]],
78
- # :ids_matrix => {0 => [0, 0], 1 => [1, 1], 2 => [2, 1]}
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(transitions:)
85
- messages = MapNestedMessages[transitions, @buffer, ENV['HIDE_GIVEN_AND_CONTINUE']]
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 transitions tracking.
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 transitions: Hash
94
- def before_interruption(exception:, transitions:)
95
- messages = MapNestedMessages[transitions, @buffer, ENV['HIDE_GIVEN_AND_CONTINUE']]
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
- backtrace = bc.clean(exception.backtrace)
107
+ dir = "#{FileUtils.pwd[1..]}/"
105
108
 
106
- puts "\nException: #{exception.message} (#{exception.class}); Backtrace: #{backtrace.join(", ")}"
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
- class BCDD::Result
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(:acc).then { _1.merge!(value.merge(_1)) } } if result.is_a?(Context)
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, expected: Context::EXPECTED_OUTCOME)
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::Result::Context
3
+ class BCDD::Context
4
4
  module Expectations::Mixin
5
5
  Factory = BCDD::Result::Expectations::Mixin::Factory
6
6
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class BCDD::Result::Context
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::Result::Context
12
+ ::BCDD::Context
13
13
  end
14
14
 
15
15
  private_class_method :mixin!, :mixin_module, :result_factory_without_expectations
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Context::Failure < BCDD::Context
4
+ include ::BCDD::Failure
5
+
6
+ def and_expose(_type, _keys, **_options)
7
+ self
8
+ end
9
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class BCDD::Result::Context
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::Result::Context
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::Result
4
- class Context::Error < BCDD::Result::Error
3
+ class BCDD::Context
4
+ class Error < BCDD::Result::Error
5
5
  InvalidExposure = ::Class.new(self)
6
6
  end
7
7
 
8
- class Context::Success < Context
9
- include ::BCDD::Result::Success::Methods
8
+ class Success < self
9
+ include ::BCDD::Success
10
10
 
11
- FetchValues = ->(acc_values, keys) do
12
- fetched_values = acc_values.fetch_values(*keys)
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: #{acc_values.keys.map(&:inspect).join(', ')}"
16
+ message = "#{e.message}. Available to expose: #{memo_values.keys.map(&:inspect).join(', ')}"
17
17
 
18
- raise Context::Error::InvalidExposure, message
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
- Transitions.tracking.reset_and_then!
26
+ EventLogs.tracking.reset_and_then!
27
27
 
28
- acc_values = acc.merge(value)
28
+ memo_values = memo.merge(value)
29
29
 
30
- value_to_expose = FetchValues.call(acc_values, keys)
30
+ value_to_expose = FetchValues.call(memo_values, keys)
31
31
 
32
32
  expectations = type_checker.expectations
33
33
 
@@ -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
@@ -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
- Transitions.tracking.record_and_then(method, injected_value) do
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::Result::Context.mixin
8
+ BCDD::Context.mixin
9
9
  BCDD::Result::Expectations.mixin
10
- BCDD::Result::Context::Expectations.mixin
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|