# frozen_string_literal: true require_relative 'immutable_struct' module Upgrow # Results are special Structs that are generated dynamically to accommodate a # set of pre-defined members. Since different Actions might want to return # zero to multiple values, they are always returned as members of a Result # instance. # # Regardless of the values the Action might want to return, a Result has one # default member called errors, which holds any errors that might occur when # the Action is performed. If Result errors are empty, the Result is a # success; if there are errors present, however, the Result is a failure. This # empowers Actions with a predictable public interface, so callers can expect # how to evaluate if an operation was successful or not by simply checking the # success or failure of a Result. # # Additionally, Result instances behave like monadic values by offering # bindings to be called only in case of success or failure, which further # simplifies the caller's code by not having to use conditional to check for # errors. class Result < ImmutableStruct class << self # Creates a new Result class that can handle the given members. # # @param members [Array] the list of members the new Result should # be able to hold. # # @return [Result] the new Result class with the given members. def new(*members) members << :errors unless members.include?(:errors) super(*members) end end # Returns a new Result instance populated with the given values. # # @param values [Hash] the list of values for each member # provided as keyword arguments. # # @return [Result] the Result instance populated with the given values. def initialize(**values) errors = values.fetch(:errors, []) super(**values.merge(errors: errors)) end # Check if the Result is successful or not. A successful Result means there # are no errors present. # # @return [true] if the Result is successful. # @return [false] if the Result is a failure. def success? errors.none? end end end