bcdd-result 0.13.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -4
  3. data/CHANGELOG.md +46 -17
  4. data/README.md +381 -220
  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 +5 -4
  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 +6 -6
  43. data/lib/bcdd/context.rb +91 -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
 
@@ -30,7 +30,8 @@ class BCDD::Result
30
30
  def self.ensure_result_object(source, value, result)
31
31
  return result.tap { result.send(:acc).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,12 +1,12 @@
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
11
  FetchValues = ->(acc_values, keys) do
12
12
  fetched_values = acc_values.fetch_values(*keys)
@@ -15,7 +15,7 @@ class BCDD::Result
15
15
  rescue ::KeyError => e
16
16
  message = "#{e.message}. Available to expose: #{acc_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,7 +23,7 @@ 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
28
  acc_values = acc.merge(value)
29
29
 
@@ -0,0 +1,91 @@
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
+ @acc = {}
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
+ acc.merge!(injected_value)
36
+
37
+ super(source, injected_value, _call: _call)
38
+ end
39
+
40
+ protected
41
+
42
+ attr_reader :acc
43
+
44
+ private
45
+
46
+ SourceMethodArity = ->(method) do
47
+ return 0 if method.arity.zero?
48
+
49
+ parameters = method.parameters.map(&:first)
50
+
51
+ return 1 if !parameters.empty? && parameters.all?(/\Akey/)
52
+
53
+ -1
54
+ end
55
+
56
+ def call_and_then_source_method!(method, injected_value)
57
+ acc.merge!(value.merge(injected_value))
58
+
59
+ case SourceMethodArity[method]
60
+ when 0 then source.send(method.name)
61
+ when 1 then source.send(method.name, **acc)
62
+ else raise Error::InvalidSourceMethodArity.build(source: source, method: method, max_arity: 1)
63
+ end
64
+ end
65
+
66
+ def call_and_then_block!(block)
67
+ acc.merge!(value)
68
+
69
+ block.call(acc)
70
+ end
71
+
72
+ def call_and_then_callable!(source, value:, injected_value:, method_name:)
73
+ acc.merge!(value.merge(injected_value))
74
+
75
+ CallableAndThen::Caller.call(source, value: acc, injected_value: injected_value, method_name: method_name)
76
+ end
77
+
78
+ def ensure_result_object(result, origin:)
79
+ raise_unexpected_outcome_error(result, origin) unless result.is_a?(BCDD::Context)
80
+
81
+ return result.tap { _1.acc.merge!(acc) } if result.source.equal?(source)
82
+
83
+ raise Error::InvalidResultSource.build(given_result: result, expected_source: source)
84
+ end
85
+
86
+ def raise_unexpected_outcome_error(result, origin)
87
+ raise Error::UnexpectedOutcome.build(outcome: result, origin: origin, expected: EXPECTED_OUTCOME)
88
+ end
89
+
90
+ private_constant :SourceMethodArity
91
+ 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|
@@ -6,20 +6,20 @@ class BCDD::Result
6
6
  OPTIONS = {
7
7
  expectations: {
8
8
  default: true,
9
- affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
9
+ affects: %w[BCDD::Result::Expectations BCDD::Context::Expectations]
10
10
  },
11
- transitions: {
11
+ event_logs: {
12
12
  default: true,
13
- affects: %w[BCDD::Result BCDD::Result::Context BCDD::Result::Expectations BCDD::Result::Context::Expectations]
13
+ affects: %w[BCDD::Result BCDD::Context BCDD::Result::Expectations BCDD::Context::Expectations]
14
14
  },
15
15
  and_then!: {
16
16
  default: false,
17
- affects: %w[BCDD::Result BCDD::Result::Context BCDD::Result::Expectations BCDD::Result::Context::Expectations]
17
+ affects: %w[BCDD::Result BCDD::Context BCDD::Result::Expectations BCDD::Context::Expectations]
18
18
  }
19
19
  }.transform_values!(&:freeze).freeze
20
20
 
21
21
  Listener = ->(option_name, _bool) do
22
- Thread.current[Transitions::THREAD_VAR_NAME] = nil if option_name == :transitions
22
+ Thread.current[EventLogs::THREAD_VAR_NAME] = nil if option_name == :event_logs
23
23
  end
24
24
 
25
25
  def self.switcher
@@ -6,7 +6,7 @@ class BCDD::Result
6
6
  OPTIONS = {
7
7
  nil_as_valid_value_checking: {
8
8
  default: false,
9
- affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
9
+ affects: %w[BCDD::Result::Expectations BCDD::Context::Expectations]
10
10
  }
11
11
  }.transform_values!(&:freeze).freeze
12
12