Class: SorbetOperation::Result

Inherits:
Object
  • Object
show all
Extended by:
T::Generic, T::Sig
Defined in:
lib/sorbet_operation/result.rb

Overview

Result is a generic class that represents the result of an operation, either success or failure.

If the result is a success, it wraps a value of type member ValueType.

If the result is a failure, it wraps an exception of type Failure.

Constant Summary collapse

ValueType =

The type of the value wrapped by the SorbetOperation::Result. The type can be any valid Sorbet type, as long as it’s a subtype of Object.

type_member { { upper: Object } }

Instance Method Summary collapse

Constructor Details

#initialize(success, value, error) ⇒ void

Constructs a new SorbetOperation::Result, either a success or a failure.

If success is true, then value must be provided (although it can be nil, because ValueType may be nilable) and error must be nil.

If success is false, then value must be nil and error must be non-nil.

Calling this constructor directly should rarely be necessary. In normal usage, Base#perform will return a SorbetOperation::Result for you.

Parameters:



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/sorbet_operation/result.rb', line 36

def initialize(success, value, error)
  @success = success
  @value = value
  @error = error

  # NOTE: these checks are annoying. A better API would be to make this
  # constructor private and provide two factory methods:
  # - `Result.success(value)`
  # - `Result.failure(error)`
  #
  # However, in order to do this, we would need to be able to use
  # {ValueType} in class methods. At this time, there is no way to tell
  # Sorbet that a generic type applies to both the class and its
  # singleton. We would need to duplicate the value type:
  # ```
  # ValueTypeMember = type_member { { upper: Object } }
  # ValueTypeTemplate = type_template { { upper: Object } }
  # ```
  # and every subclass would need to specify both (and ensure that
  # they're both set to the same type). This would be quite clumsy. Since
  # `Result` should rarely be instantiated directly (rather, it's
  # instantiated by `SorbetOperation::Base#perform`), we'll just live with
  # this less than ideal API for now.
  if @success
    # We can't test that value is not nil because the value type can be
    # nilable. (In theory we could check if the type is nilable and only
    # apply the check if it's not, but that's not worth the complexity.)
    unless error.nil?
      raise ArgumentError, "Cannot pass an error to a success result"
    end
  elsif error.nil?
    raise ArgumentError, "Must pass an error to a failure result"
  elsif !value.nil?
    raise ArgumentError, "Cannot pass a value to a failure result"
  end
end

Instance Method Details

#failure?Boolean

Returns true if the result is a failure.

Returns:

  • (Boolean)


81
82
83
# File 'lib/sorbet_operation/result.rb', line 81

def failure?
  !success?
end

#on_failure(&blk) {|T.must(@error)| ... } ⇒ T.self_type

Yields the contained error if the result is a failure, otherwise does nothing. Returns self so this call can be chained to #on_success.

Examples:

SomeOperation.new.perform
 .on_success { |value| puts "Success! Value: #{value}" }
 .on_failure { |error| puts "Failure! Error: #{error}" }

Parameters:

  • blk (T.proc.params(error: Failure).void)

Yields:

  • (T.must(@error))

Returns:

  • (T.self_type)


173
174
175
176
# File 'lib/sorbet_operation/result.rb', line 173

def on_failure(&blk)
  yield(T.must(@error)) if failure?
  self
end

#on_success(&blk) {|casted_value| ... } ⇒ T.self_type

Yields the contained value if the result is a success, otherwise does nothing. Returns self so this call can be chained to #on_failure.

Examples:

SomeOperation.new.perform
 .on_success { |value| puts "Success! Value: #{value}" }
 .on_failure { |error| puts "Failure! Error: #{error}" }

Parameters:

Yields:

  • (casted_value)

Returns:

  • (T.self_type)


160
161
162
163
# File 'lib/sorbet_operation/result.rb', line 160

def on_success(&blk)
  yield(casted_value) if success?
  self
end

#safe_unwrapValueType?

Returns the contained value if the result is a success, otherwise returns nil.

Returns:



97
98
99
100
101
# File 'lib/sorbet_operation/result.rb', line 97

def safe_unwrap
  return if failure?

  casted_value
end

#safe_unwrap_errorFailure?

Returns the contained error if the result is a failure, otherwise returns nil.

Returns:



146
147
148
149
150
# File 'lib/sorbet_operation/result.rb', line 146

def safe_unwrap_error
  return T.must(@error) if failure?

  nil
end

#success?Boolean

Returns true if the result is a success.

Returns:

  • (Boolean)


75
76
77
# File 'lib/sorbet_operation/result.rb', line 75

def success?
  @success
end

#unwrap!ValueType

Returns the contained value if the result is a success, otherwise raises the contained error.

Returns:



88
89
90
91
92
# File 'lib/sorbet_operation/result.rb', line 88

def unwrap!
  raise T.must(@error) if failure?

  casted_value
end

#unwrap_error!Failure

Returns the contained error if the result is a failure, otherwise raises an error.

Returns:



136
137
138
139
140
141
# File 'lib/sorbet_operation/result.rb', line 136

def unwrap_error!
  return T.must(@error) if failure?

  # TODO: custom error type?
  raise "Called `unwrap_err!` on a success"
end

#unwrap_or(default) ⇒ ValueType

Returns the contained value if the result is a success, otherwise returns the provided default value.

Examples:

result = SomeOperation.new.perform
result.failure? # => true
value = result.unwrap_or(456)
value # => 456

Parameters:

Returns:



112
113
114
115
116
# File 'lib/sorbet_operation/result.rb', line 112

def unwrap_or(default)
  return casted_value if success?

  default
end

#unwrap_or_else(&blk) {|T.must(@error)| ... } ⇒ ValueType

Returns the contained value if the result is a success, otherwise calls the block with the contained error and returns the block’s return value.

Examples:

result = SomeOperation.new.perform
result.failure? # => true
value = result.unwrap_or_else { |_| 456 }
value # => 456

Parameters:

Yields:

  • (T.must(@error))

Returns:



127
128
129
130
131
# File 'lib/sorbet_operation/result.rb', line 127

def unwrap_or_else(&blk)
  return casted_value if success?

  yield(T.must(@error))
end