Module: ShopifyCLI::Result
- Defined in:
- lib/shopify_cli/result.rb
Overview
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 ‘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..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. } # => "Boom!"
Defined Under Namespace
Classes: Error, Failure, Success, UnexpectedFailure, UnexpectedSuccess
Class Method Summary collapse
-
.call ⇒ Object
Wraps the given block and invokes it with the passed arguments.
-
.failure(error) ⇒ Object
wraps the given value into a ‘ShopifyCLI::Result::Failure` container.
-
.success(value) ⇒ Object
wraps the given value into a ‘ShopifyCLI::Result::Success` container.
-
.wrap ⇒ Object
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.
Class Method Details
.call ⇒ Object
Wraps the given block and invokes it with the passed arguments.
442 443 444 445 |
# File 'lib/shopify_cli/result.rb', line 442 ruby2_keywords def call(*args, &block) raise ArgumentError, "expected a block" unless block wrap(&block).call(*args) end |
.failure(error) ⇒ Object
wraps the given value into a ‘ShopifyCLI::Result::Failure` container
#### Parameters
-
‘error` a value of arbitrary type
376 377 378 |
# File 'lib/shopify_cli/result.rb', line 376 def self.failure(error) Result::Failure.new(error) end |
.success(value) ⇒ Object
wraps the given value into a ‘ShopifyCLI::Result::Success` container
#### Parameters
-
‘value` a value of arbitrary type
365 366 367 |
# File 'lib/shopify_cli/result.rb', line 365 def self.success(value) Result::Success.new(value) end |
.wrap ⇒ Object
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
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 |
# File 'lib/shopify_cli/result.rb', line 414 ruby2_keywords def wrap(*values, &block) raise ArgumentError, "expected either a value or a block" unless (values.length == 1) ^ block if values.length == 1 values.pop.yield_self do |value| case value when Result::Success, Result::Failure value when NilClass, Exception Result.failure(value) else Result.success(value) end end else ->(*args) do begin wrap(block.call(*args)) rescue Exception => error # rubocop:disable Lint/RescueException wrap(error) end end.ruby2_keywords end end |