# frozen_string_literal: true require_relative 'result/version' require_relative 'result/error' require_relative 'result/data' require_relative 'result/handler' require_relative 'result/failure' require_relative 'result/success' require_relative 'result/mixin' require_relative 'result/expectations' class BCDD::Result attr_accessor :unknown attr_reader :subject, :data, :type_checker protected :subject private :unknown, :unknown=, :type_checker def initialize(type:, value:, subject: nil, expectations: nil) data = Data.new(name, type, value) @type_checker = Expectations.evaluate(data, expectations) @subject = subject @data = data self.unknown = true end def type data.type end def value data.value end def success?(_type = nil) raise Error::NotImplemented end def failure?(_type = nil) raise Error::NotImplemented end def value_or(&_block) raise Error::NotImplemented end def on(*types, &block) raise Error::MissingTypeArgument if types.empty? tap { known(block) if type_checker.allow?(types) } end def on_success(*types, &block) tap { known(block) if type_checker.allow_success?(types) && success? } end def on_failure(*types, &block) tap { known(block) if type_checker.allow_failure?(types) && failure? } end def on_unknown tap { yield(value, type) if unknown } end def and_then(method_name = nil, context = nil) return self if failure? method_name && block_given? and raise ArgumentError, 'method_name and block are mutually exclusive' return call_subject_method(method_name, context) if method_name result = yield(value) ensure_result_object(result, origin: :block) end def handle handler = Handler.new(self, type_checker: type_checker) yield handler handler.send(:outcome) end def ==(other) self.class == other.class && type == other.type && value == other.value end def hash [self.class, type, value].hash end def inspect format('#<%s type=%p value=%p>', class_name: self.class.name, type: type, value: value) end def deconstruct [type, value] end def deconstruct_keys(_keys) { name => { type => value } } end alias eql? == alias on_type on private def name :unknown end def known(block) self.unknown = false block.call(value, type) end def call_subject_method(method_name, context) method = subject.method(method_name) result = case method.arity when 0 then subject.send(method_name) when 1 then subject.send(method_name, value) when 2 then subject.send(method_name, value, context) else raise Error::WrongSubjectMethodArity.build(subject: subject, method: method) end ensure_result_object(result, origin: :method) end def ensure_result_object(result, origin:) raise Error::UnexpectedOutcome.build(outcome: result, origin: origin) unless result.is_a?(::BCDD::Result) return result if result.subject.equal?(subject) raise Error::WrongResultSubject.build(given_result: result, expected_subject: subject) end end