# frozen_string_literal: true require 'set' module Micro class Case class Result require 'micro/case/result/wrapper' require 'micro/case/result/transitions' INVALID_INVOCATION_OF_THE_THEN_METHOD = Error::InvalidInvocationOfTheThenMethod.new("#{self.name}#") @@transitions_enabled = true def self.transitions_enabled? @@transitions_enabled end attr_reader :type, :data, :use_case alias value data def initialize(transitions_mapper = Transitions::MapEverything) enable_transitions = @@transitions_enabled @__is_unknown = true @__accumulated_data = {} @__tracked_use_cases = Set.new @__accessible_attributes = {} @__transitions = enable_transitions ? [] : Kind::Empty::ARRAY @__transitions_mapper = transitions_mapper if enable_transitions end def inspect pretty_type = @__success ? 'Success' : 'Failure' instance_info = '%s (%s) type=:%s data=%s' % [pretty_type, self.class, @type, data] transitions_info = ' transitions=%d' % [@__transitions.size] if Micro::Case::Result.transitions_enabled? "#<#{instance_info}#{transitions_info}>" end def to_ary [data, type] end def to_sym @__success ? :success : :failure end def [](key) data[key] end def values_at(*keys) data.values_at(*keys) end def key?(key) data.key?(key) end def value?(value) data.value?(value) end def slice(*keys) Utils::Hashes.slice(data, keys) end def success? @__success end def failure? !success? end def unknown? @__is_unknown end def accessible_attributes @__accessible_attributes.keys end def on_success(expected_type = nil) return self unless __success_type?(expected_type) @__is_unknown = false hook_data = expected_type.nil? ? self : data yield(hook_data, @use_case) self end def on_failure(expected_type = nil) return self unless __failure_type?(expected_type) @__is_unknown = false hook_data = expected_type.nil? ? self : data yield(hook_data, @use_case) self end def on_exception(expected_exception = nil) return self unless __failure_type?(:exception) if !expected_exception || (Kind.is?(Exception, expected_exception) && data.fetch(:exception).is_a?(expected_exception)) @__is_unknown = false yield(data, @use_case) end self end def on_unknown return self unless unknown? yield(self, @use_case) self end def then(use_case = nil, attributes = nil, &block) can_yield_self = respond_to?(:yield_self) if block raise INVALID_INVOCATION_OF_THE_THEN_METHOD if use_case raise NotImplementedError if !can_yield_self yield_self(&block) else return yield_self if !use_case && can_yield_self return failure? ? self : __call_use_case(use_case, attributes) end end def |(arg) failure? ? self : __call_use_case(arg) end def transitions @__transitions.dup end FetchData = -> (data) do return data if data.is_a?(Hash) return { data => true } if data.is_a?(Symbol) { exception: data } if data.is_a?(Exception) end def __set__(is_success, data, type, use_case) raise Error::InvalidResultType unless type.is_a?(Symbol) raise Error::InvalidUseCase unless use_case.is_a?(::Micro::Case) @__success, @type, @use_case = is_success, type, use_case @data = FetchData.call(data).freeze raise Micro::Case::Error::InvalidResult.new(is_success, type, use_case) unless @data @__accumulated_data.merge!(@data) use_case_attributes = Utils::Hashes.symbolize_keys(@use_case.attributes) unless @__tracked_use_cases.member?(use_case_class = @use_case.class) @__tracked_use_cases.add(use_case_class) __update_accessible_attributes(use_case_attributes) end __set_transition(use_case_attributes) unless @__transitions.frozen? self end def __set_accessible_attributes__(arg) return arg unless arg.is_a?(Hash) attributes = Utils::Hashes.symbolize_keys(arg) __update_accessible_attributes(attributes) __fetch_accessible_attributes end private def __update_accessible_attributes(attributes) @__accessible_attributes.merge!(attributes) end def __fetch_accessible_attributes @__accessible_attributes.dup end def __call_use_case(use_case, attributes = nil) case use_case when Proc __call_proc(use_case, 'then(-> {})'.freeze) when Method __call_method(use_case, attributes) when String, Symbol use_case_method = self.use_case.method(use_case) __call_method(use_case_method, attributes) else raise INVALID_INVOCATION_OF_THE_THEN_METHOD unless ::Micro.case_or_flow?(use_case) input = attributes.is_a?(Hash) ? self.data.merge(attributes) : self.data if use_case.is_a?(::Micro::Cases::Flow) use_case.call!(input: input, result: self) else use_case.__new__(self, input).__call__ end end end def __call_proc(fn, expected) __update_accessible_attributes(@__accumulated_data) result = fn.arity.zero? ? fn.call : fn.call(__fetch_accessible_attributes) return self if result === self raise Error::UnexpectedResult.new("#{Result.name}##{expected}") end def __call_method(methd, attributes = nil) __update_accessible_attributes(attributes ? attributes.merge(@__accumulated_data) : @__accumulated_data) result = methd.arity.zero? ? methd.call : methd.call(**__fetch_accessible_attributes) return self if result === self raise Error::UnexpectedResult.new("#{use_case.class.name}#method(:#{methd.name})") end def __success_type?(expected_type) success? && (expected_type.nil? || expected_type == type) end def __failure_type?(expected_type) failure? && (expected_type.nil? || expected_type == type) end def __set_transition(use_case_attributes) @__transitions << @__transitions_mapper.call(self, use_case_attributes) end private_constant :FetchData, :INVALID_INVOCATION_OF_THE_THEN_METHOD end end end