Skip to content
This repository has been archived by the owner on Jun 1, 2023. It is now read-only.

module ShopifyCli::Result

Konstantin Tennhard edited this page Feb 1, 2021 · 4 revisions

This module defines two containers for wrapping the result of an action. One for signifying the successful execution of an action and one for signifying a failure. Both containers implement the same API, which has been designed to simplify transforming a result through a series of steps and centralize the error handling in one place. The implementation is heavily inspired by a concept known as result monads in other languages. Consider the following example that uses lambda expressions as stand-ins for more complex method objects:

require 'open-uri'
Todo = Struct.new(:title, :completed)

fetch_data = ->(url) { open(url) }
parse_data = ->(json) { JSON.parse(json) }
build_todo = ->(attrs) do
  Todo.new(attrs.fetch(:title), attrs.fetch(:completed))
end

Result.wrap(&fetch_data)
  .call("https://jsonplaceholder.typicode.com/todos/1")
  .then(&parse_data)
  .then(&build_todo)
  .map(&:title)
  .unwrap(nil) # => String | nil

If everything goes well, this code returns the title of the to do that is being fetched from https://jsonplaceholder.typicode.com/todos/1. However, there are several possible failure scenarios:

  • fetching the data could fail due to a network error,
  • the data returned from the server might not be valid JSON, or
  • the data is valid but does not have the right shape.

If any of these scenarios arises, all subsequent then and map blocks are skipped until the result is either unwrapped or we manually recover from the failure by specifying a rescue clause:

Result.wrap { raise "Boom!" }
  .rescue { |e| e.message.upcase }
  .unwrap(nil) # => "BOOM!"

In the event of a failure that hasn't been rescued from, unwrap returns the fallback value specified by the caller:

Result.wrap { raise "Boom!" }.unwrap(nil) # => nil
Result.wrap { raise "Boom!" }.unwrap { |e| e.message } # => "Boom!"

Class Methods

success

success(value) wraps the given value into a ShopifyCli::Result::Success container

Parameters

  • value a value of arbitrary type
see source

# File lib/shopify-cli/result.rb, line 351
def self.success(value)
  Result::Success.new(value)
end

failure

failure(error) wraps the given value into a ShopifyCli::Result::Failure container

Parameters

  • error a value of arbitrary type
see source

# File lib/shopify-cli/result.rb, line 362
def self.failure(error)
  Result::Failure.new(error)
end

wrap

wrap(*args, &block) takes either a value or a block and chooses the appropriate result container based on the type of the value or the type of the block's return value. If the type is an exception, it is wrapped in a ShopifyCli::Result::Failure and otherwise in a ShopifyCli::Result::Success. If a block was provided instead of value, a Proc is returned and the result wrapping doesn't occur until the block is invoked.

Parameters

  • *args should be an Array with zero or one element
  • &block should be a Proc that takes zero or one argument

Returns

Returns either a Result::Success, Result::Failure or a Proc that produces one of the former when invoked.

Examples

Result.wrap(1) # => ShopifyCli::Result::Success
Result.wrap(RuntimeError.new) # => ShopifyCli::Result::Failure

Result.wrap { 1 } # => Proc
Result.wrap { 1 }.call # => ShopifyCli::Result::Success
Result.wrap { raise }.call # => ShopifyCli::Result::Failure

Result.wrap { |s| s.upcase }.call("hello").tap do |result|
  result # => Result::Success
  result.value # => "HELLO"
end
see source

# File lib/shopify-cli/result.rb, line 399
def self.wrap(*args, &block)
  raise ArgumentError, "expected either a value or a block" unless args.one? ^ block

  if args.one?
    args.pop.yield_self do |value|
      case value
      when Result::Success, Result::Failure
        value
      when Exception
        Result.failure(value)
      else
        Result.success(value)
      end
    end
  else
    ->(value = nil) do
      begin
        wrap(block.call(value))
      rescue => error
        wrap(error)
      end
    end
  end
end

Clone this wiki locally