-
Notifications
You must be signed in to change notification settings - Fork 201
class ShopifyCli::Result::Success
Implements a container for wrapping a success value. The main purpose of the container is to support further transformations of the result and centralize error handling should any of the subsequent transformations fail:
result = Result
.new("{}")
.then { |json| JSON.parse(json) }
.tap do |result|
result.success? # => true
result.value # => {}
.then { |data| data.fetch(:firstname) }
.tap do |result|
result.failure? # => true
result.error # => KeyError
end
Success
implements two transformation functions: then
and map
. The
former makes no assumption regarding the return value of the transformation.
The latter on the other hand expects the transformation to be successful. If
this assumption is violated, program execution is interrupted and an error is
raised. As the purpose of result objects is to guard against exactly that.
This is generally a flaw and requires the code to either be hardened or to
substitute the call to map
with a call to then
. map
should only be used
for transformations that cannot fail and when the caller wants to state
exactly that fact.
value
new(value)
initializes a new Success
from an arbitrary value.
see source
# File lib/shopify-cli/result.rb, line 88
def initialize(value)
@value = value
end
success?()
always returns true to indicate that this result represents a success.
see source
# File lib/shopify-cli/result.rb, line 95
def success?
true
end
failure?()
always returns false to indicate that this result represents a success.
see source
# File lib/shopify-cli/result.rb, line 102
def failure?
false
end
error()
raises an UnexpectedSuccess
as a Success
does not carry an error value.
see source
# File lib/shopify-cli/result.rb, line 110
def error
raise UnexpectedSuccess
end
map(&block)
returns a new Success
wrapping the result of the given block. The block is
called with the current value. If the block raises an exception or returns a
Failure
, an exception is raised. map
assumes any transformation to
succeed. Transformations that are expected to fail under certain conditions
should only be transformed using then
:
Success
.new(nil)
.map { |n| n + 1 } # => raises NoMethodError
Therefore, map should only be used here if the previous success value is guaranteed to be a number or if the block handles nil cases properly:
Success
.new(nil)
.map { |n| (n || 0) + 1 }
.value # => 1
see source
# File lib/shopify-cli/result.rb, line 133
def map(&block)
self.then(&block).tap do |result|
return result if result.success?
result.unwrap { |error| error }.tap do |error|
case error
when Exception
raise error
else
raise UnexpectedFailure, error
end
end
end
end
then(&block)
returns a new result by wrapping the return value of the block. The block is
invoked with the current success value. The result can either be a Success
or a Failure
. The former is the default. The latter occurs when executing
the block either
- raised an exception,
- returned an instance of a subclass of
Exception
, or - returned a
Failure
.
The example below illustrates this behavior:
result = Success
.new(1)
.then { |n| n + 1 }
.tap do |result|
result.success? # => true
result.value # => 2
end
result.then { |n| n / 0 }.error # => ZeroDivisionError
result.then { RuntimeError.new }.error # => RuntimeError
result.then { Failure.new("Boom!") }.error # => "Boom!"
see source
# File lib/shopify-cli/result.rb, line 172
def then(&block)
Result.wrap(&block).call(@value)
end
rescue()
is a no-op and simply returns itself. Only a Failure
can be transformed
using rescue
.
see source
# File lib/shopify-cli/result.rb, line 180
def rescue
self
end
unwrap(*args, &block)
returns the success value and ignores the fallback value that was either
provided as a method argument or by passing a block. However, the caller is
still required to specify a fallback value to ensure that in the event of a
Failure
program execution can continue in a controlled manner:
Success.new(1).unwrap(0) => 1
see source
# File lib/shopify-cli/result.rb, line 193
def unwrap(*args, &block)
raise ArgumentError, "expected either a fallback value or a block" unless (args.length == 1) ^ block
@value
end