Class: Errgonomic::Result::Any
- Includes:
- Comparable
- Defined in:
- lib/errgonomic/result.rb
Overview
The base class for Result's Ok and Err class variants. We implement as much logic as possible here, including construction, and let Ok and Err handle only their self identification.
Constant Summary collapse
- RUST_SPELLINGS =
Rust spellings we accept but do not advertise: they delegate to the Ruby-idiomatic predicate and nudge the caller there via stderr.
{ is_ok: :ok?, is_err: :err?, is_ok_and: :ok_and?, is_err_and: :err_and? }.freeze
Instance Method Summary collapse
-
#!=(other) ⇒ Object
Ruby derives != from ==, so a strict-equality message would name the operator the caller did not write.
-
#<=>(other) ⇒ Object
Results order like Rust's: Ok sorts before any Err, and same variants order by their inner values.
-
#==(other) ⇒ Object
A Result equals another Result of the same variant with an equal inner value.
-
#===(other) ⇒ Object
Object#=== is ==, so a
case value when Ok(1)and a pinned pattern reach the same check, named for the operator that was written. -
#and(other) ⇒ Object
Given another result, return it if the inner result is Ok, else return the inner Err.
-
#and_then(&block) ⇒ Object
Given a block, evaluate it and return its result if the inner result is Ok, else return the inner Err.
-
#as_json(*_args) ⇒ Object
ActiveSupport's Hash#as_json and Array#as_json recurse through their members with as_json rather than to_json, so a Result nested in a payload reaches Object#as_json and serializes as its instance variables.
-
#deconstruct ⇒ Object
The Rust shape: each variant deconstructs to its one payload, so
in Ok(v)binds the value andin Err(e)binds the error. -
#eql?(other) ⇒ Boolean
Hash-based collections (Hash keys, Set, uniq, group_by) use eql? and hash, not ==.
-
#err_and?(&block) ⇒ Boolean
Return true if the inner value is an Err and the result of the block is truthy.
-
#expect!(msg = nil, &block) ⇒ Object
Return the inner value of an Ok, else raise an exception with the given message when Err.
- #hash ⇒ Object
-
#initialize(value) ⇒ Any
constructor
A Result is a value, not a slot: the inner value is reached through a combinator that handles the other variant, and nothing swaps it out from under another reference.
-
#map(&block) ⇒ Object
Map the Ok(a) to an Ok(b), preserving the Err.
-
#map_err(&block) ⇒ Object
Map the Err(e) to an Err(f), preserving the Ok.
-
#method_missing(name, *args, &block) ⇒ Object
A Result deliberately forwards nothing to its inner value, so a miss here is almost always someone treating the container as its contents.
-
#ok_and?(&block) ⇒ Boolean
Return true if the inner value is an Ok and the result of the block is truthy.
-
#or(other) ⇒ Object
Return other if self is Err, else return the original Option.
-
#or_else(&block) ⇒ Object
Return self if it is Ok, else lazy evaluate the block and return its result.
-
#pretty_print(pp) ⇒ Object
pp uses its own object dump unless told otherwise; keep it consistent with inspect.
- #respond_to_missing?(name, include_private = false) ⇒ Boolean
-
#result? ⇒ Boolean
Indicate that this is some kind of result object.
-
#tap_err(&block) ⇒ Object
Calls the function with the inner error value, if Err, but returns the original Result.
-
#tap_ok(&block) ⇒ Object
Calls the function with the inner ok value, if Ok, while returning the original Result.
-
#to_json(*_args) ⇒ Object
Refuse to serialize an unwrapped Result as JSON.
-
#to_s ⇒ Object
Refuse to render as a String.
-
#unwrap! ⇒ Object
Return the inner value of an Ok, else raise an exception when Err.
-
#unwrap_err! ⇒ Object
Return the inner value of an Err, else raise an exception when Ok.
-
#unwrap_or(other) ⇒ Object
Return the inner value if self is Ok, else return the provided default.
-
#unwrap_or_else(&block) ⇒ Object
Return the inner value if self is Ok, else lazy evaluate the block and return its result.
Constructor Details
#initialize(value) ⇒ Any
A Result is a value, not a slot: the inner value is reached through a combinator that handles the other variant, and nothing swaps it out from under another reference.
31 32 33 34 |
# File 'lib/errgonomic/result.rb', line 31 def initialize(value) @value = value freeze end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args, &block) ⇒ Object
A Result deliberately forwards nothing to its inner value, so a miss here is almost always someone treating the container as its contents. Teach the route out instead of leaving a bare NoMethodError. Rust spellings of the predicates delegate, with a nudge on stderr.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/errgonomic/result.rb', line 90 def method_missing(name, *args, &block) if (canonical = RUST_SPELLINGS[name]) warn "Errgonomic: `#{name}` is the Rust spelling; prefer `#{canonical}`. Delegating." return public_send(canonical, *args, &block) end raise Errgonomic::UnwrappedAccessError.new(<<~MSG, name) undefined method `#{name}' for #{inspect}, a Result, which does not forward methods to its inner value. Reach for a combinator instead: map, map_err, and_then, or_else: transform the value or the error unwrap_or, unwrap_or_else: supply a fallback ok_and?, err_and?: test a predicate against the inner value unwrap!, unwrap_err!, and expect! also exist, but are intended for tests rather than application code. MSG end |
Instance Method Details
#!=(other) ⇒ Object
Ruby derives != from ==, so a strict-equality message would name the operator the caller did not write.
174 175 176 177 |
# File 'lib/errgonomic/result.rb', line 174 def !=(other) strict_equality!(other, '!=') super end |
#<=>(other) ⇒ Object
Results order like Rust's: Ok sorts before any Err, and same variants order by their inner values. Two Results whose inner values do not themselves compare follow Ruby's convention and answer nil. A non-Result operand raises instead: Comparable turns a nil here into an ArgumentError that names the Result as the operand at fault, where what went wrong is that a wrapper was ordered against a bare value.
54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/errgonomic/result.rb', line 54 def <=>(other) unless other.is_a?(Errgonomic::Result::Any) raise Errgonomic::TypeMismatchError, "cannot compare #{inspect} with #{other.class}; test the inner value " \ '(ok_and? { |v| v <= other }) or reach for it (map, unwrap_or)' end return ok? ? -1 : 1 if self.class != other.class value <=> other.value end |
#==(other) ⇒ Object
A Result equals another Result of the same variant with an equal inner value. Comparing it with anything that is not a Result raises Errgonomic::TypeMismatchError, on the terms Option#== states: the raise reaches ==, !=, eql? and ===, hashing stays quiet, and a bare value on the left answers for itself.
139 140 141 142 143 144 |
# File 'lib/errgonomic/result.rb', line 139 def ==(other) strict_equality!(other, '==') return false if self.class != other.class value == other.value end |
#===(other) ⇒ Object
Object#=== is ==, so a case value when Ok(1) and a pinned pattern
reach the same check, named for the operator that was written.
148 149 150 151 |
# File 'lib/errgonomic/result.rb', line 148 def ===(other) strict_equality!(other, '===') self == other end |
#and(other) ⇒ Object
Given another result, return it if the inner result is Ok, else return the inner Err. Raise an exception if the other value is not a Result.
282 283 284 285 286 287 |
# File 'lib/errgonomic/result.rb', line 282 def and(other) raise Errgonomic::ArgumentError, 'other must be a Result' unless other.is_a?(Errgonomic::Result::Any) return self if err? other end |
#and_then(&block) ⇒ Object
Given a block, evaluate it and return its result if the inner result is Ok, else return the inner Err. This is lazy evaluated, and we pedantically check the type of the block's return value at runtime. This is annoying, sorry, but better than an "undefined method" error. Hopefully it gives your test suite a chance to detect incorrect usage.
302 303 304 305 306 307 308 309 310 311 |
# File 'lib/errgonomic/result.rb', line 302 def and_then(&block) return self if err? res = block.call(value) if !res.is_a?(Errgonomic::Result::Any) && !Errgonomic.give_me_ambiguous_downstream_errors? raise Errgonomic::ArgumentError, 'and_then block must return a Result' end res end |
#as_json(*_args) ⇒ Object
ActiveSupport's Hash#as_json and Array#as_json recurse through their members with as_json rather than to_json, so a Result nested in a payload reaches Object#as_json and serializes as its instance variables. Refuse there too, and the guard holds wherever a Result travels.
460 461 462 |
# File 'lib/errgonomic/result.rb', line 460 def as_json(*_args) raise Errgonomic::SerializeError, 'cannot serialize an unwrapped Result' end |
#deconstruct ⇒ Object
The Rust shape: each variant deconstructs to its one payload, so
in Ok(v) binds the value and in Err(e) binds the error.
541 542 543 |
# File 'lib/errgonomic/result.rb', line 541 def deconstruct [value] end |
#eql?(other) ⇒ Boolean
Hash-based collections (Hash keys, Set, uniq, group_by) use eql? and hash, not ==. Follow the inner value's own eql? semantics, so Results behave as keys exactly like their inner values.
167 168 169 170 |
# File 'lib/errgonomic/result.rb', line 167 def eql?(other) strict_equality!(other, 'eql?') self.class == other.class && value.eql?(other.value) end |
#err_and?(&block) ⇒ Boolean
Return true if the inner value is an Err and the result of the block is truthy.
221 222 223 224 225 226 227 |
# File 'lib/errgonomic/result.rb', line 221 def err_and?(&block) if err? !!block.call(value) else false end end |
#expect!(msg = nil, &block) ⇒ Object
Return the inner value of an Ok, else raise an exception with the given message when Err. A block is called only on the Err branch, so a message that interpolates costs nothing on the path that succeeds.
250 251 252 253 254 |
# File 'lib/errgonomic/result.rb', line 250 def expect!(msg = nil, &block) raise Errgonomic::ExpectError, block ? block.call : msg unless ok? @value end |
#hash ⇒ Object
182 183 184 |
# File 'lib/errgonomic/result.rb', line 182 def hash [self.class, value].hash end |
#map(&block) ⇒ Object
Map the Ok(a) to an Ok(b), preserving the Err
408 409 410 411 412 |
# File 'lib/errgonomic/result.rb', line 408 def map(&block) return self if err? Ok(block.call(value)) end |
#map_err(&block) ⇒ Object
Map the Err(e) to an Err(f), preserving the Ok
419 420 421 422 423 |
# File 'lib/errgonomic/result.rb', line 419 def map_err(&block) return self if ok? Err(block.call(value)) end |
#ok_and?(&block) ⇒ Boolean
Return true if the inner value is an Ok and the result of the block is truthy.
207 208 209 210 211 |
# File 'lib/errgonomic/result.rb', line 207 def ok_and?(&block) return false if err? !!block.call(value) end |
#or(other) ⇒ Object
Return other if self is Err, else return the original Option. Raises a pedantic runtime exception if other is not a Result.
322 323 324 325 326 327 328 329 330 |
# File 'lib/errgonomic/result.rb', line 322 def or(other) unless other.is_a?(Errgonomic::Result::Any) raise Errgonomic::ArgumentError, 'other must be a Result; you might want unwrap_or' end return other if err? self end |
#or_else(&block) ⇒ Object
Return self if it is Ok, else lazy evaluate the block and return its result. Raises a pedantic runtime check that the block returns a Result. Sorry about that, hopefully it helps your tests. Better than ambiguous downstream "undefined method" errors, probably.
344 345 346 347 348 349 350 351 352 353 |
# File 'lib/errgonomic/result.rb', line 344 def or_else(&block) return self if ok? res = block.call(value) if !res.is_a?(Errgonomic::Result::Any) && !Errgonomic.give_me_ambiguous_downstream_errors? raise Errgonomic::ArgumentError, 'or_else block must return a Result' end res end |
#pretty_print(pp) ⇒ Object
pp uses its own object dump unless told otherwise; keep it consistent with inspect.
466 467 468 |
# File 'lib/errgonomic/result.rb', line 466 def pretty_print(pp) pp.text(inspect) end |
#respond_to_missing?(name, include_private = false) ⇒ Boolean
106 107 108 |
# File 'lib/errgonomic/result.rb', line 106 def respond_to_missing?(name, include_private = false) RUST_SPELLINGS.key?(name) || super end |
#result? ⇒ Boolean
Indicate that this is some kind of result object. Contrast to Object#result? which is false for all other types.
193 194 195 |
# File 'lib/errgonomic/result.rb', line 193 def result? true end |
#tap_err(&block) ⇒ Object
Calls the function with the inner error value, if Err, but returns the original Result.
391 392 393 394 |
# File 'lib/errgonomic/result.rb', line 391 def tap_err(&block) block.call(value) if err? self end |
#tap_ok(&block) ⇒ Object
Calls the function with the inner ok value, if Ok, while returning the original Result.
398 399 400 401 |
# File 'lib/errgonomic/result.rb', line 398 def tap_ok(&block) block.call(value) if ok? self end |
#to_json(*_args) ⇒ Object
Refuse to serialize an unwrapped Result as JSON. Not only should we require that Results be correctly handled to access their inner value, but without this we will get undefined structures from default Object#to_json implementations.
451 452 453 |
# File 'lib/errgonomic/result.rb', line 451 def to_json(*_args) raise Errgonomic::SerializeError, 'cannot serialize an unwrapped Result' end |
#to_s ⇒ Object
Refuse to render as a String. Rust gives Result a Debug and no Display: a wrapper that reaches a string went unhandled, and a string is where it turns into data, a hostname, a hash key or a page. The refusal names the value and says how to log it or take it.
439 440 441 |
# File 'lib/errgonomic/result.rb', line 439 def to_s raise Errgonomic::SerializeError, to_s_refusal end |
#unwrap! ⇒ Object
Return the inner value of an Ok, else raise an exception when Err.
234 235 236 237 238 |
# File 'lib/errgonomic/result.rb', line 234 def unwrap! raise Errgonomic::UnwrapError.new('value is an Err', @value) unless ok? @value end |
#unwrap_err! ⇒ Object
Return the inner value of an Err, else raise an exception when Ok. The message is the Ok's value as inspect renders it, bounded, so an Ok holding an Option or a Result still has a message to print.
265 266 267 268 269 |
# File 'lib/errgonomic/result.rb', line 265 def unwrap_err! raise Errgonomic::UnwrapError.new(bounded_inspect(value), value) unless err? @value end |
#unwrap_or(other) ⇒ Object
Return the inner value if self is Ok, else return the provided default.
362 363 364 365 366 |
# File 'lib/errgonomic/result.rb', line 362 def unwrap_or(other) return value if ok? other end |
#unwrap_or_else(&block) ⇒ Object
Return the inner value if self is Ok, else lazy evaluate the block and return its result.
376 377 378 379 380 |
# File 'lib/errgonomic/result.rb', line 376 def unwrap_or_else(&block) return value if ok? block.call(value) end |