diff --git a/lib/bcdd/result/context/success.rb b/lib/bcdd/result/context/success.rb index ce74df5e..1abc71b4 100644 --- a/lib/bcdd/result/context/success.rb +++ b/lib/bcdd/result/context/success.rb @@ -1,9 +1,23 @@ # frozen_string_literal: true class BCDD::Result + class Context::Error < BCDD::Result::Error + InvalidExposure = ::Class.new(self) + end + class Context::Success < Context include ::BCDD::Result::Success::Methods + FetchValues = ->(acc_values, keys) do + fetched_values = acc_values.fetch_values(*keys) + + keys.zip(fetched_values).to_h + rescue ::KeyError => e + message = "#{e.message}. Available to expose: #{acc_values.keys.map(&:inspect).join(', ')}" + + raise Context::Error::InvalidExposure, message + end + def and_expose(type, keys, terminal: true) unless keys.is_a?(::Array) && !keys.empty? && keys.all?(::Symbol) raise ::ArgumentError, 'keys must be an Array of Symbols' @@ -11,9 +25,11 @@ def and_expose(type, keys, terminal: true) Transitions.tracking.reset_and_then! - exposed_value = acc.merge(value).slice(*keys) + acc_values = acc.merge(value) + + value_to_expose = FetchValues.call(acc_values, keys) - self.class.new(type: type, value: exposed_value, source: source, terminal: terminal) + self.class.new(type: type, value: value_to_expose, source: source, terminal: terminal) end end end diff --git a/sig/bcdd/result/context.rbs b/sig/bcdd/result/context.rbs index 49321630..1cbb0f29 100644 --- a/sig/bcdd/result/context.rbs +++ b/sig/bcdd/result/context.rbs @@ -28,10 +28,19 @@ class BCDD::Result::Context < BCDD::Result def raise_unexpected_outcome_error: (BCDD::Result::Context | untyped, Symbol) -> void end +class BCDD::Result::Context + class Error < BCDD::Result::Error + class InvalidExposure < BCDD::Result::Context::Error + end + end +end + class BCDD::Result::Context class Success < BCDD::Result::Context include BCDD::Result::Success::Methods + FetchValues: Proc + def and_expose: (Symbol, Array[Symbol], terminal: bool) -> BCDD::Result::Context::Success end diff --git a/test/bcdd/result/context/and_expose/invalid_keys_test.rb b/test/bcdd/result/context/and_expose/invalid_keys_test.rb new file mode 100644 index 00000000..49bd35c3 --- /dev/null +++ b/test/bcdd/result/context/and_expose/invalid_keys_test.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'test_helper' + +class BCDD::Result + class ContextAndExposeInvalidKeysTest < Minitest::Test + class Divide + include BCDD::Result::Context.mixin + + def call(arg1, arg2) + validate_numbers(arg1, arg2) + .and_then(:divide, extra_division: 2) + .and_expose(:division_completed, %i[final_numbers extra_division number1 number2]) + end + + private + + def validate_numbers(arg1, arg2) + arg1.is_a?(Numeric) or return Failure(:invalid_arg, message: 'arg1 must be numeric') + arg2.is_a?(Numeric) or return Failure(:invalid_arg, message: 'arg2 must be numeric') + + Success(:ok, number1: arg1, number2: arg2) + end + + def divide(number1:, number2:, extra_division:) + Success(:division_completed, final_number: (number1 / number2) / extra_division) + end + end + + test '#and_expose receive an invalid key' do + err = assert_raises(BCDD::Result::Context::Error::InvalidExposure) { Divide.new.call(12, 2) } + + assert err.message.start_with?('key not found: :final_numbers') + assert err.message.end_with?('. Available to expose: :number1, :number2, :extra_division, :final_number') + end + end +end