bcdd-result 0.11.0 → 0.12.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.
@@ -6,6 +6,9 @@ class BCDD::Result
6
6
  require_relative 'context/success'
7
7
  require_relative 'context/mixin'
8
8
  require_relative 'context/expectations'
9
+ require_relative 'context/callable_and_then'
10
+
11
+ EXPECTED_OUTCOME = 'BCDD::Result::Context::Success or BCDD::Result::Context::Failure'
9
12
 
10
13
  def self.Success(type, **value)
11
14
  Success.new(type: type, value: value)
@@ -15,7 +18,7 @@ class BCDD::Result
15
18
  Failure.new(type: type, value: value)
16
19
  end
17
20
 
18
- def initialize(type:, value:, subject: nil, expectations: nil, terminal: nil)
21
+ def initialize(type:, value:, source: nil, expectations: nil, terminal: nil)
19
22
  value.is_a?(::Hash) or raise ::ArgumentError, 'value must be a Hash'
20
23
 
21
24
  @acc = {}
@@ -23,8 +26,16 @@ class BCDD::Result
23
26
  super
24
27
  end
25
28
 
26
- def and_then(method_name = nil, **context_data, &block)
27
- super(method_name, context_data, &block)
29
+ def and_then(method_name = nil, **injected_value, &block)
30
+ super(method_name, injected_value, &block)
31
+ end
32
+
33
+ def and_then!(source, **injected_value)
34
+ _call = injected_value.delete(:_call)
35
+
36
+ acc.merge!(injected_value)
37
+
38
+ super(source, injected_value, _call: _call)
28
39
  end
29
40
 
30
41
  protected
@@ -33,20 +44,23 @@ class BCDD::Result
33
44
 
34
45
  private
35
46
 
36
- SubjectMethodArity = ->(method) do
47
+ SourceMethodArity = ->(method) do
37
48
  return 0 if method.arity.zero?
38
- return 1 if method.parameters.map(&:first).all?(/\Akey/)
49
+
50
+ parameters = method.parameters.map(&:first)
51
+
52
+ return 1 if !parameters.empty? && parameters.all?(/\Akey/)
39
53
 
40
54
  -1
41
55
  end
42
56
 
43
- def call_and_then_subject_method!(method, context_data)
44
- acc.merge!(value.merge(context_data))
57
+ def call_and_then_source_method!(method, injected_value)
58
+ acc.merge!(value.merge(injected_value))
45
59
 
46
- case SubjectMethodArity[method]
47
- when 0 then subject.send(method.name)
48
- when 1 then subject.send(method.name, **acc)
49
- else raise Error::InvalidSubjectMethodArity.build(subject: subject, method: method, max_arity: 1)
60
+ case SourceMethodArity[method]
61
+ when 0 then source.send(method.name)
62
+ when 1 then source.send(method.name, **acc)
63
+ else raise Error::InvalidSourceMethodArity.build(source: source, method: method, max_arity: 1)
50
64
  end
51
65
  end
52
66
 
@@ -56,20 +70,24 @@ class BCDD::Result
56
70
  block.call(acc)
57
71
  end
58
72
 
73
+ def call_and_then_callable!(source, value:, injected_value:, method_name:)
74
+ acc.merge!(value.merge(injected_value))
75
+
76
+ CallableAndThen::Caller.call(source, value: acc, injected_value: injected_value, method_name: method_name)
77
+ end
78
+
59
79
  def ensure_result_object(result, origin:)
60
80
  raise_unexpected_outcome_error(result, origin) unless result.is_a?(Context)
61
81
 
62
- return result.tap { _1.acc.merge!(acc) } if result.subject.equal?(subject)
82
+ return result.tap { _1.acc.merge!(acc) } if result.source.equal?(source)
63
83
 
64
- raise Error::InvalidResultSubject.build(given_result: result, expected_subject: subject)
84
+ raise Error::InvalidResultSource.build(given_result: result, expected_source: source)
65
85
  end
66
86
 
67
- EXPECTED_OUTCOME = 'BCDD::Result::Context::Success or BCDD::Result::Context::Failure'
68
-
69
87
  def raise_unexpected_outcome_error(result, origin)
70
88
  raise Error::UnexpectedOutcome.build(outcome: result, origin: origin, expected: EXPECTED_OUTCOME)
71
89
  end
72
90
 
73
- private_constant :SubjectMethodArity, :EXPECTED_OUTCOME
91
+ private_constant :SourceMethodArity
74
92
  end
75
93
  end
@@ -9,7 +9,7 @@ class BCDD::Result::Error < StandardError
9
9
  end
10
10
 
11
11
  class MissingTypeArgument < self
12
- def initialize(_arg = nil)
12
+ def initialize(_message = nil)
13
13
  super('A type (argument) is required to invoke the #on/#on_type method')
14
14
  end
15
15
  end
@@ -22,29 +22,38 @@ class BCDD::Result::Error < StandardError
22
22
  end
23
23
  end
24
24
 
25
- class InvalidResultSubject < self
26
- def self.build(given_result:, expected_subject:)
25
+ class InvalidResultSource < self
26
+ def self.build(given_result:, expected_source:)
27
27
  message =
28
- "You cannot call #and_then and return a result that does not belong to the subject!\n" \
29
- "Expected subject: #{expected_subject.inspect}\n" \
30
- "Given subject: #{given_result.send(:subject).inspect}\n" \
28
+ "You cannot call #and_then and return a result that does not belong to the same source!\n" \
29
+ "Expected source: #{expected_source.inspect}\n" \
30
+ "Given source: #{given_result.send(:source).inspect}\n" \
31
31
  "Given result: #{given_result.inspect}"
32
32
 
33
33
  new(message)
34
34
  end
35
35
  end
36
36
 
37
- class InvalidSubjectMethodArity < self
38
- def self.build(subject:, method:, max_arity:)
39
- new("#{subject.class}##{method.name} has unsupported arity (#{method.arity}). Expected 0..#{max_arity}")
37
+ class InvalidSourceMethodArity < self
38
+ def self.build(source:, method:, max_arity:)
39
+ new("#{source.class}##{method.name} has unsupported arity (#{method.arity}). Expected 0..#{max_arity}")
40
40
  end
41
41
  end
42
42
 
43
43
  class UnhandledTypes < self
44
44
  def self.build(types:)
45
- subject = types.size == 1 ? 'This was' : 'These were'
45
+ source = types.size == 1 ? 'This was' : 'These were'
46
46
 
47
- new("You must handle all cases. #{subject} not handled: #{types.map(&:inspect).join(', ')}")
47
+ new("You must handle all cases. #{source} not handled: #{types.map(&:inspect).join(', ')}")
48
+ end
49
+ end
50
+
51
+ class CallableAndThenDisabled < self
52
+ def initialize(_message = nil)
53
+ super(
54
+ 'You cannot use #and_then! as the feature is disabled. ' \
55
+ 'Please use BCDD::Result.config.feature.enable!(:and_then!) to enable it.'
56
+ )
48
57
  end
49
58
  end
50
59
  end
@@ -24,7 +24,7 @@ class BCDD::Result
24
24
 
25
25
  FACTORY = <<~RUBY
26
26
  private def _Result
27
- @_Result ||= Result.with(subject: self, terminal: %<terminal>s)
27
+ @_Result ||= Result.with(source: self, terminal: %<terminal>s)
28
28
  end
29
29
  RUBY
30
30
 
@@ -38,13 +38,13 @@ class BCDD::Result
38
38
  module Addons
39
39
  module Continue
40
40
  private def Continue(value)
41
- Success.new(type: :continued, value: value, subject: self)
41
+ Success.new(type: :continued, value: value, source: self)
42
42
  end
43
43
  end
44
44
 
45
45
  module Given
46
46
  private def Given(value)
47
- Success.new(type: :given, value: value, subject: self)
47
+ Success.new(type: :given, value: value, source: self)
48
48
  end
49
49
  end
50
50
 
@@ -38,10 +38,10 @@ class BCDD::Result
38
38
 
39
39
  private_class_method :mixin!, :mixin_module, :result_factory_without_expectations
40
40
 
41
- def initialize(subject: nil, contract: nil, terminal: nil, **options)
41
+ def initialize(source: nil, contract: nil, terminal: nil, **options)
42
42
  @terminal = terminal
43
43
 
44
- @subject = subject
44
+ @source = source
45
45
 
46
46
  @contract = contract if contract.is_a?(Contract::Evaluator)
47
47
 
@@ -60,16 +60,16 @@ class BCDD::Result
60
60
  _ResultAs(Failure, type, value)
61
61
  end
62
62
 
63
- def with(subject:, terminal: nil)
64
- self.class.new(subject: subject, terminal: terminal, contract: contract)
63
+ def with(source:, terminal: nil)
64
+ self.class.new(source: source, terminal: terminal, contract: contract)
65
65
  end
66
66
 
67
67
  private
68
68
 
69
69
  def _ResultAs(kind_class, type, value)
70
- kind_class.new(type: type, value: value, subject: subject, expectations: contract, terminal: terminal)
70
+ kind_class.new(type: type, value: value, source: source, expectations: contract, terminal: terminal)
71
71
  end
72
72
 
73
- attr_reader :subject, :terminal, :contract
73
+ attr_reader :source, :terminal, :contract
74
74
  end
75
75
  end
@@ -21,7 +21,7 @@ class BCDD::Result
21
21
  end
22
22
 
23
23
  private def _ResultAs(kind_class, type, value, terminal: nil)
24
- kind_class.new(type: type, value: value, subject: self, terminal: terminal)
24
+ kind_class.new(type: type, value: value, source: self, terminal: terminal)
25
25
  end
26
26
  end
27
27
 
@@ -2,16 +2,26 @@
2
2
 
3
3
  module BCDD::Result::Transitions
4
4
  module Tracking::Disabled
5
- def self.start(name:, desc:); end
6
-
7
- def self.finish(result:); end
5
+ def self.exec(_name, _desc)
6
+ EnsureResult[yield]
7
+ end
8
8
 
9
9
  def self.reset!; end
10
10
 
11
11
  def self.record(result); end
12
12
 
13
- def self.record_and_then(_type, _data, _subject)
13
+ def self.record_and_then(_type, _data, _source)
14
14
  yield
15
15
  end
16
+
17
+ def self.reset_and_then!; end
18
+
19
+ class << self
20
+ private
21
+
22
+ def start(name, desc); end
23
+
24
+ def finish(result); end
25
+ end
16
26
  end
17
27
  end
@@ -6,26 +6,18 @@ module BCDD::Result::Transitions
6
6
 
7
7
  private :tree, :tree=, :records, :records=, :root_started_at, :root_started_at=
8
8
 
9
- def start(name:, desc:)
10
- name_and_desc = [name, desc]
11
-
12
- tree.frozen? ? root_start(name_and_desc) : tree.insert!(name_and_desc)
13
- end
14
-
15
- def finish(result:)
16
- node = tree.current
9
+ def exec(name, desc)
10
+ start(name, desc)
17
11
 
18
- tree.move_up!
12
+ transition_node = tree.current
19
13
 
20
- return unless node.root?
14
+ result = EnsureResult[yield]
21
15
 
22
- duration = (now_in_milliseconds - root_started_at)
16
+ tree.move_to_root! if transition_node.root?
23
17
 
24
- metadata = { duration: duration, tree_map: tree.nested_ids }
18
+ finish(result)
25
19
 
26
- result.send(:transitions=, version: Tracking::VERSION, records: records, metadata: metadata)
27
-
28
- reset!
20
+ result
29
21
  end
30
22
 
31
23
  def reset!
@@ -38,11 +30,11 @@ module BCDD::Result::Transitions
38
30
  track(result, time: ::Time.now.getutc)
39
31
  end
40
32
 
41
- def record_and_then(type_arg, arg, subject)
33
+ def record_and_then(type_arg, arg, source)
42
34
  type = type_arg.instance_of?(::Method) ? :method : type_arg
43
35
 
44
36
  unless tree.frozen?
45
- current_and_then = { type: type, arg: arg, subject: subject }
37
+ current_and_then = { type: type, arg: arg, source: source }
46
38
  current_and_then[:method_name] = type_arg.name if type == :method
47
39
 
48
40
  tree.current.value[1] = current_and_then
@@ -51,8 +43,36 @@ module BCDD::Result::Transitions
51
43
  yield
52
44
  end
53
45
 
46
+ def reset_and_then!
47
+ return if tree.frozen?
48
+
49
+ tree.current.value[1] = Tracking::EMPTY_HASH
50
+ end
51
+
54
52
  private
55
53
 
54
+ def start(name, desc)
55
+ name_and_desc = [name, desc]
56
+
57
+ tree.frozen? ? root_start(name_and_desc) : tree.insert!(name_and_desc)
58
+ end
59
+
60
+ def finish(result)
61
+ node = tree.current
62
+
63
+ tree.move_up!
64
+
65
+ return unless node.root?
66
+
67
+ duration = (now_in_milliseconds - root_started_at)
68
+
69
+ metadata = { duration: duration, tree_map: tree.nested_ids }
70
+
71
+ result.send(:transitions=, version: Tracking::VERSION, records: records, metadata: metadata)
72
+
73
+ reset!
74
+ end
75
+
56
76
  TreeNodeValueNormalizer = ->(id, (nam, des)) { [{ id: id, name: nam, desc: des }, Tracking::EMPTY_HASH] }
57
77
 
58
78
  def root_start(name_and_desc)
@@ -74,7 +94,7 @@ module BCDD::Result::Transitions
74
94
  end
75
95
 
76
96
  def now_in_milliseconds
77
- Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
97
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
78
98
  end
79
99
  end
80
100
  end
@@ -46,9 +46,9 @@ class BCDD::Result
46
46
  def initialize(value, normalizer: ->(_id, val) { val })
47
47
  @size = 0
48
48
 
49
- @root = Node.new(value, parent: nil, id: @size, normalizer: normalizer)
49
+ @root = Node.new(value, parent: nil, id: size, normalizer: normalizer)
50
50
 
51
- @current = @root
51
+ @current = root
52
52
  end
53
53
 
54
54
  def root_value
@@ -70,19 +70,23 @@ class BCDD::Result
70
70
  end
71
71
 
72
72
  def insert!(value)
73
- @current = insert(value)
73
+ move_to! insert(value)
74
+ end
75
+
76
+ def move_to!(node)
77
+ tap { @current = node }
74
78
  end
75
79
 
76
80
  def move_up!(level = 1)
77
- tap { level.times { @current = current.parent || root } }
81
+ tap { level.times { move_to!(current.parent || root) } }
78
82
  end
79
83
 
80
84
  def move_down!(level = 1, index: -1)
81
- tap { level.times { current.children[index].then { |child| @current = child if child } } }
85
+ tap { level.times { current.children[index].then { |child| move_to!(child) if child } } }
82
86
  end
83
87
 
84
88
  def move_to_root!
85
- tap { @current = root }
89
+ move_to!(root)
86
90
  end
87
91
 
88
92
  NestedIds = ->(node) { [node.id, node.children.map(&NestedIds)] }
@@ -7,21 +7,19 @@ class BCDD::Result
7
7
 
8
8
  THREAD_VAR_NAME = :bcdd_result_transitions_tracking
9
9
 
10
+ EnsureResult = ->(result) do
11
+ return result if result.is_a?(::BCDD::Result)
12
+
13
+ raise Error::UnexpectedOutcome.build(outcome: result, origin: :transitions)
14
+ end
15
+
10
16
  def self.tracking
11
17
  Thread.current[THREAD_VAR_NAME] ||= Tracking.instance
12
18
  end
13
19
  end
14
20
 
15
- def self.transitions(name: nil, desc: nil)
16
- Transitions.tracking.start(name: name, desc: desc)
17
-
18
- result = yield
19
-
20
- result.is_a?(::BCDD::Result) or raise Error::UnexpectedOutcome.build(outcome: result, origin: :transitions)
21
-
22
- Transitions.tracking.finish(result: result)
23
-
24
- result
21
+ def self.transitions(name: nil, desc: nil, &block)
22
+ Transitions.tracking.exec(name, desc, &block)
25
23
  rescue ::Exception => e
26
24
  Transitions.tracking.reset!
27
25
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BCDD
4
4
  class Result
5
- VERSION = '0.11.0'
5
+ VERSION = '0.12.0'
6
6
  end
7
7
  end
data/lib/bcdd/result.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'singleton'
4
+
3
5
  require_relative 'result/version'
4
- require_relative 'result/transitions'
5
6
  require_relative 'result/error'
7
+ require_relative 'result/transitions'
8
+ require_relative 'result/callable_and_then'
6
9
  require_relative 'result/data'
7
10
  require_relative 'result/handler'
8
11
  require_relative 'result/failure'
@@ -16,9 +19,9 @@ require_relative 'result/config'
16
19
  class BCDD::Result
17
20
  attr_accessor :unknown, :transitions
18
21
 
19
- attr_reader :subject, :data, :type_checker, :terminal
22
+ attr_reader :source, :data, :type_checker, :terminal
20
23
 
21
- protected :subject
24
+ protected :source
22
25
 
23
26
  private :unknown, :unknown=, :type_checker, :transitions=
24
27
 
@@ -32,11 +35,11 @@ class BCDD::Result
32
35
  config.freeze
33
36
  end
34
37
 
35
- def initialize(type:, value:, subject: nil, expectations: nil, terminal: nil)
38
+ def initialize(type:, value:, source: nil, expectations: nil, terminal: nil)
36
39
  data = Data.new(kind, type, value)
37
40
 
38
41
  @type_checker = Contract.evaluate(data, expectations)
39
- @subject = subject
42
+ @source = source
40
43
  @terminal = terminal || kind == :failure
41
44
  @data = data
42
45
 
@@ -88,12 +91,20 @@ class BCDD::Result
88
91
  tap { yield(value, type) if unknown }
89
92
  end
90
93
 
91
- def and_then(method_name = nil, context = nil, &block)
94
+ def and_then(method_name = nil, injected_value = nil, &block)
92
95
  return self if terminal?
93
96
 
94
97
  method_name && block and raise ::ArgumentError, 'method_name and block are mutually exclusive'
95
98
 
96
- method_name ? call_and_then_subject_method(method_name, context) : call_and_then_block(block)
99
+ method_name ? call_and_then_source_method(method_name, injected_value) : call_and_then_block(block)
100
+ end
101
+
102
+ def and_then!(source, injected_value = nil, _call: nil)
103
+ raise Error::CallableAndThenDisabled unless Config.instance.feature.enabled?(:and_then!)
104
+
105
+ return self if terminal?
106
+
107
+ call_and_then_callable!(source, value: value, injected_value: injected_value, method_name: _call)
97
108
  end
98
109
 
99
110
  def handle
@@ -139,27 +150,27 @@ class BCDD::Result
139
150
  block.call(value, type)
140
151
  end
141
152
 
142
- def call_and_then_subject_method(method_name, context_data)
143
- method = subject.method(method_name)
153
+ def call_and_then_source_method(method_name, injected_value)
154
+ method = source.method(method_name)
144
155
 
145
- Transitions.tracking.record_and_then(method, context_data, subject) do
146
- result = call_and_then_subject_method!(method, context_data)
156
+ Transitions.tracking.record_and_then(method, injected_value, source) do
157
+ result = call_and_then_source_method!(method, injected_value)
147
158
 
148
159
  ensure_result_object(result, origin: :method)
149
160
  end
150
161
  end
151
162
 
152
- def call_and_then_subject_method!(method, context_data)
163
+ def call_and_then_source_method!(method, injected_value)
153
164
  case method.arity
154
- when 0 then subject.send(method.name)
155
- when 1 then subject.send(method.name, value)
156
- when 2 then subject.send(method.name, value, context_data)
157
- else raise Error::InvalidSubjectMethodArity.build(subject: subject, method: method, max_arity: 2)
165
+ when 0 then source.send(method.name)
166
+ when 1 then source.send(method.name, value)
167
+ when 2 then source.send(method.name, value, injected_value)
168
+ else raise Error::InvalidSourceMethodArity.build(source: source, method: method, max_arity: 2)
158
169
  end
159
170
  end
160
171
 
161
172
  def call_and_then_block(block)
162
- Transitions.tracking.record_and_then(:block, nil, subject) do
173
+ Transitions.tracking.record_and_then(:block, nil, source) do
163
174
  result = call_and_then_block!(block)
164
175
 
165
176
  ensure_result_object(result, origin: :block)
@@ -170,11 +181,15 @@ class BCDD::Result
170
181
  block.call(value)
171
182
  end
172
183
 
184
+ def call_and_then_callable!(source, value:, injected_value:, method_name:)
185
+ CallableAndThen::Caller.call(source, value: value, injected_value: injected_value, method_name: method_name)
186
+ end
187
+
173
188
  def ensure_result_object(result, origin:)
174
189
  raise Error::UnexpectedOutcome.build(outcome: result, origin: origin) unless result.is_a?(::BCDD::Result)
175
190
 
176
- return result if result.subject.equal?(subject)
191
+ return result if result.source.equal?(source)
177
192
 
178
- raise Error::InvalidResultSubject.build(given_result: result, expected_subject: subject)
193
+ raise Error::InvalidResultSource.build(given_result: result, expected_source: source)
179
194
  end
180
195
  end
@@ -0,0 +1,60 @@
1
+ module BCDD::Result::CallableAndThen
2
+ class Config
3
+ attr_accessor default_method_name_to_call: Symbol
4
+
5
+ def initialize: -> void
6
+
7
+ def options: () -> Hash[Symbol, untyped]
8
+ end
9
+
10
+ class Error < BCDD::Result::Error
11
+ end
12
+
13
+ class Error::InvalidArity < Error
14
+ def self.build: (
15
+ source: untyped,
16
+ method: Symbol,
17
+ arity: String
18
+ ) -> Error::InvalidArity
19
+ end
20
+
21
+ class Caller
22
+ def self.call: (
23
+ untyped source,
24
+ value: untyped,
25
+ injected_value: untyped,
26
+ method_name: (Symbol | nil)
27
+ ) -> BCDD::Result
28
+
29
+ private
30
+
31
+ def self.call_proc!: (
32
+ untyped source,
33
+ untyped value,
34
+ untyped injected_value
35
+ ) -> BCDD::Result
36
+
37
+ def self.call_method!: (
38
+ untyped source,
39
+ Method method,
40
+ untyped value,
41
+ untyped injected_value
42
+ ) -> BCDD::Result
43
+
44
+ def self.callable_method: (
45
+ untyped source,
46
+ (Symbol | nil) method_name
47
+ ) -> ::Method
48
+
49
+ def self.ensure_result_object: (
50
+ untyped source,
51
+ untyped value,
52
+ BCDD::Result result
53
+ ) -> BCDD::Result
54
+
55
+ def self.expected_result_object: () -> singleton(BCDD::Result)
56
+
57
+ def self.expected_outcome: () -> String
58
+ end
59
+ end
60
+
@@ -14,6 +14,8 @@ class BCDD::Result::Config
14
14
 
15
15
  def initialize: -> void
16
16
 
17
+ def and_then!: () -> BCDD::Result::CallableAndThen::Config
18
+
17
19
  def freeze: -> BCDD::Result::Config
18
20
  def options: -> Hash[Symbol, BCDD::Result::Config::Switcher]
19
21
  def to_h: -> Hash[Symbol, Hash[Symbol | String, bool]]