`Result` refresh (#96)
* `Result` refresh * add full support for `Result[T, void]` (aka `Opt[T]` aka `Option[T]`) * expand tests * add `flatten`, `filter` of `Option` fame * add `tryError` that raises a regular exception when result holds a value * fix `$` to print `ok`/`err` in lower-case, like the functions that created the result * add `orErr` that collapses all errors to a single value of a potentially different type - useful when translating errors between layers * `capture` should work with `CatchableError` * remove `Defect`-dependent tests * Update stew/results.nim * avoid redundant error message when converting error to string * avoid multiple evaluation in `valueOr` * add `unsafeError` to match `unsafeGet` * let `valueOr` evaluate a block * add `errorOr` to mirror `valueOr`
This commit is contained in:
parent
17cd8c846f
commit
d2ae2889e8
562
stew/results.nim
562
stew/results.nim
|
@ -22,8 +22,13 @@ type
|
|||
## # Example
|
||||
##
|
||||
## ```
|
||||
## import stew/results
|
||||
##
|
||||
## # Re-export `results` so that API is always available to users of your module!
|
||||
## export results
|
||||
##
|
||||
## # It's convenient to create an alias - most likely, you'll do just fine
|
||||
## # with strings or cstrings as error
|
||||
## # with strings or cstrings as error for a start
|
||||
##
|
||||
## type R = Result[int, string]
|
||||
##
|
||||
|
@ -33,9 +38,13 @@ type
|
|||
## # ok says it went... ok!
|
||||
## R.ok 42
|
||||
## func fails(): R =
|
||||
## # or type it like this, to not repeat the type!
|
||||
## # or type it like this, to not repeat the type:
|
||||
## result.err "bad luck"
|
||||
##
|
||||
## func alsoWorks(): R =
|
||||
## # or just use the shortcut - auto-deduced from the return type!
|
||||
## ok(24)
|
||||
##
|
||||
## if (let w = works(); w.isOk):
|
||||
## echo w[], " or use value: ", w.value
|
||||
##
|
||||
|
@ -45,20 +54,29 @@ type
|
|||
## a, b, c
|
||||
## type RE[T] = Result[T, Error]
|
||||
##
|
||||
## # In the expriments corner, you'll find the following syntax for passing
|
||||
## # errors up the stack:
|
||||
## # You can use the question mark operator to pass errors up the call stack
|
||||
## func f(): R =
|
||||
## let x = ?works() - ?fails()
|
||||
## assert false, "will never reach"
|
||||
##
|
||||
## # If you provide this exception converter, this exception will be raised
|
||||
## # on dereference
|
||||
## # If you provide this exception converter, this exception will be raised on
|
||||
## # `tryGet`:
|
||||
## func toException(v: Error): ref CatchableError = (ref CatchableError)(msg: $v)
|
||||
## try:
|
||||
## RE[int].err(a)[]
|
||||
## RE[int].err(a).tryGet()
|
||||
## except CatchableError:
|
||||
## echo "in here!"
|
||||
##
|
||||
## # You can use `Opt[T]` as a replacement for `Option` = `Opt` is an alias for
|
||||
## # `Result[T, void]`, meaning you can use the full `Result` API on it:
|
||||
## let x = Opt[int].ok(42)
|
||||
## echo x.get()
|
||||
##
|
||||
## # ... or `Result[void, E]` as a replacement for `bool`, providing extra error
|
||||
## # information!
|
||||
## let y = Resul[void, string].err("computation failed")
|
||||
## echo y.error()
|
||||
##
|
||||
## ```
|
||||
##
|
||||
## See the tests for more practical examples, specially when working with
|
||||
|
@ -144,16 +162,53 @@ type
|
|||
## meta-data collection a visible part of your API in another way - this
|
||||
## way it remains discoverable by the caller!
|
||||
##
|
||||
## A natural "error API" progression is starting with `Option[T]`, then
|
||||
## A natural "error API" progression is starting with `Opt[T]`, then
|
||||
## `Result[T, cstring]`, `Result[T, enum]` and `Result[T, object]` in
|
||||
## escalating order of complexity.
|
||||
##
|
||||
## # Result equivalences with other types
|
||||
##
|
||||
## Result allows tightly controlling the amount of information that a
|
||||
## function gives to the caller:
|
||||
##
|
||||
## ## `Result[void, void] == bool`
|
||||
##
|
||||
## Neither value nor error information, it either worked or didn't. Most
|
||||
## often used for `proc`:s with side effects.
|
||||
##
|
||||
## ## `Result[T, void] == Option[T]`
|
||||
##
|
||||
## Return value if it worked, else tell the caller it failed. Most often
|
||||
## used for simple computiations.
|
||||
##
|
||||
## Works as a fully replacement for `Option[T]` (aliased as `Opt[T]`)
|
||||
##
|
||||
## ## `Result[T, E]` -
|
||||
##
|
||||
## Return value if it worked, or a statically known piece of information
|
||||
## when it didn't - most often used when a function can fail in more than
|
||||
## one way - E is typically a `string` or an `enum`.
|
||||
##
|
||||
## ## `Result[T, ref E]`
|
||||
##
|
||||
## Returning a `ref E` allows introducing dynamically typed error
|
||||
## information, similar to exceptions.
|
||||
##
|
||||
## # Other implemenations in nim
|
||||
##
|
||||
## There are other implementations in nim that you might prefer:
|
||||
## * Either from nimfp: https://github.com/vegansk/nimfp/blob/master/src/fp/either.nim
|
||||
## * result_type: https://github.com/kapralos/result_type/
|
||||
##
|
||||
## `Option` compatibility
|
||||
##
|
||||
## `Result[T, void]` is similar to `Option[T]`, except it can be used with
|
||||
## all `Result` operators and helpers.
|
||||
##
|
||||
## One difference is `Option[ref|ptr T]` which disallows `nil` - `Opt[T]`
|
||||
## allows an "ok" result to hold `nil` - this can be useful when `nil` is
|
||||
## a valid outcome of a function, but increases complexity for the caller.
|
||||
##
|
||||
## # Implementation notes
|
||||
##
|
||||
## This implementation is mostly based on the one in rust. Compared to it,
|
||||
|
@ -257,6 +312,14 @@ type
|
|||
|
||||
Opt*[T] = Result[T, void]
|
||||
|
||||
func raiseResultOk[T, E](self: Result[T, E]) {.noreturn, noinline.} =
|
||||
# noinline because raising should take as little space as possible at call
|
||||
# site
|
||||
when T is void:
|
||||
raise (ref ResultError[void])(msg: "Trying to access error with value")
|
||||
else:
|
||||
raise (ref ResultError[T])(msg: "Trying to access error with value", error: self.v)
|
||||
|
||||
func raiseResultError[T, E](self: Result[T, E]) {.noreturn, noinline.} =
|
||||
# noinline because raising should take as little space as possible at call
|
||||
# site
|
||||
|
@ -266,13 +329,15 @@ func raiseResultError[T, E](self: Result[T, E]) {.noreturn, noinline.} =
|
|||
if self.e.isNil: # for example Result.default()!
|
||||
raise (ref ResultError[void])(msg: "Trying to access value with err (nil)")
|
||||
raise self.e
|
||||
elif E is void:
|
||||
raise (ref ResultError[void])(msg: "Trying to access value with err")
|
||||
elif compiles(toException(self.e)):
|
||||
raise toException(self.e)
|
||||
elif compiles($self.e):
|
||||
raise (ref ResultError[E])(
|
||||
error: self.e, msg: "Trying to access value with err: " & $self.e)
|
||||
error: self.e, msg: $self.e)
|
||||
else:
|
||||
raise (res ResultError[E])(msg: "Trying to access value with err", error: self.e)
|
||||
raise (ref ResultError[E])(msg: "Trying to access value with err", error: self.e)
|
||||
|
||||
func raiseResultDefect(m: string, v: auto) {.noreturn, noinline.} =
|
||||
mixin `$`
|
||||
|
@ -283,23 +348,34 @@ func raiseResultDefect(m: string) {.noreturn, noinline.} =
|
|||
raise (ref ResultDefect)(msg: m)
|
||||
|
||||
template assertOk(self: Result) =
|
||||
# Careful - `self` evaluated multiple times, which is fine in all current uses
|
||||
if not self.o:
|
||||
when self.E isnot void:
|
||||
raiseResultDefect("Trying to access value with err Result", self.e)
|
||||
else:
|
||||
raiseResultDefect("Trying to access value with err Result")
|
||||
|
||||
template ok*[T, E](R: type Result[T, E], x: auto): R =
|
||||
template ok*[T, E](R: type Result[T, E], x: untyped): R =
|
||||
## Initialize a result with a success and value
|
||||
## Example: `Result[int, string].ok(42)`
|
||||
R(o: true, v: x)
|
||||
|
||||
template ok*[T, E](self: var Result[T, E], x: auto) =
|
||||
template ok*[E](R: type Result[void, E]): R =
|
||||
## Initialize a result with a success and value
|
||||
## Example: `Result[void, string].ok()`
|
||||
R(o: true)
|
||||
|
||||
template ok*[T: not void, E](self: var Result[T, E], x: untyped) =
|
||||
## Set the result to success and update value
|
||||
## Example: `result.ok(42)`
|
||||
self = ok(type self, x)
|
||||
|
||||
template err*[T, E](R: type Result[T, E], x: auto): R =
|
||||
template ok*[E](self: var Result[void, E]) =
|
||||
## Set the result to success and update value
|
||||
## Example: `result.ok()`
|
||||
self = (type self).ok()
|
||||
|
||||
template err*[T, E](R: type Result[T, E], x: untyped): R =
|
||||
## Initialize the result to an error
|
||||
## Example: `Result[int, string].err("uh-oh")`
|
||||
R(o: false, e: x)
|
||||
|
@ -307,13 +383,15 @@ template err*[T, E](R: type Result[T, E], x: auto): R =
|
|||
template err*[T](R: type Result[T, cstring], x: string): R =
|
||||
## Initialize the result to an error
|
||||
## Example: `Result[int, string].err("uh-oh")`
|
||||
const s = x
|
||||
const s = x # avoid dangling cstring pointers
|
||||
R(o: false, e: cstring(s))
|
||||
|
||||
template err*[T](R: type Result[T, void]): R =
|
||||
## Initialize the result to an error
|
||||
## Example: `Result[int, void].err()`
|
||||
R(o: false)
|
||||
|
||||
template err*[T, E](self: var Result[T, E], x: auto) =
|
||||
template err*[T, E](self: var Result[T, E], x: untyped) =
|
||||
## Set the result as an error
|
||||
## Example: `result.err("uh-oh")`
|
||||
self = err(type self, x)
|
||||
|
@ -328,59 +406,164 @@ template err*[T](self: var Result[T, void]) =
|
|||
self = err(type self)
|
||||
|
||||
template ok*(v: auto): auto = ok(typeof(result), v)
|
||||
template ok*(): auto = ok(typeof(result))
|
||||
|
||||
template err*(v: auto): auto = err(typeof(result), v)
|
||||
template err*(): auto = err(typeof(result))
|
||||
|
||||
template isOk*(self: Result): bool = self.o
|
||||
template isErr*(self: Result): bool = not self.o
|
||||
|
||||
template isSome*(o: Opt): bool =
|
||||
## Alias for `isOk`
|
||||
isOk o
|
||||
|
||||
template isNone*(o: Opt): bool =
|
||||
## Alias of `isErr`
|
||||
isErr o
|
||||
|
||||
func map*[T, E, A](
|
||||
self: Result[T, E], f: proc(x: T): A): Result[A, E] {.inline.} =
|
||||
func map*[T0, E, T1](
|
||||
self: Result[T0, E], f: proc(x: T0): T1): Result[T1, E] {.inline.} =
|
||||
## Transform value using f, or return error
|
||||
##
|
||||
## ```
|
||||
## let r = Result[int, cstring).ok(42)
|
||||
## assert r.map(proc (v: int): int = $v).get() == "42"
|
||||
## ```
|
||||
if self.o: result.ok(f(self.v))
|
||||
else: result.err(self.e)
|
||||
if self.o:
|
||||
result.ok(f(self.v))
|
||||
else:
|
||||
when E is void:
|
||||
result.err()
|
||||
else:
|
||||
result.err(self.e)
|
||||
|
||||
func flatMap*[T, E, A](
|
||||
self: Result[T, E], f: proc(x: T): Result[A, E]): Result[A, E] {.inline.} =
|
||||
func map*[T, E](
|
||||
self: Result[T, E], f: proc(x: T)): Result[void, E] {.inline.} =
|
||||
## Transform value using f, or return error
|
||||
##
|
||||
## ```
|
||||
## let r = Result[int, cstring).ok(42)
|
||||
## assert r.map(proc (v: int): int = $v).get() == "42"
|
||||
## ```
|
||||
if self.o:
|
||||
f(self.v)
|
||||
result.ok()
|
||||
else:
|
||||
when E is void:
|
||||
result.err()
|
||||
else:
|
||||
result.err(self.e)
|
||||
|
||||
func map*[E, T1](
|
||||
self: Result[void, E], f: proc(): T1): Result[T1, E] {.inline.} =
|
||||
## Transform value using f, or return error
|
||||
if self.o:
|
||||
result.ok(f())
|
||||
else:
|
||||
when E is void:
|
||||
result.err()
|
||||
else:
|
||||
result.err(self.e)
|
||||
|
||||
func map*[E](
|
||||
self: Result[void, E], f: proc()): Result[void, E] {.inline.} =
|
||||
## Call f if value is
|
||||
if self.o:
|
||||
f()
|
||||
result.ok()
|
||||
else:
|
||||
when E is void:
|
||||
result.err()
|
||||
else:
|
||||
result.err(self.e)
|
||||
|
||||
func flatMap*[T0, E, T1](
|
||||
self: Result[T0, E], f: proc(x: T0): Result[T1, E]): Result[T1, E] {.inline.} =
|
||||
if self.o: f(self.v)
|
||||
else: Result[A, E].err(self.e)
|
||||
else:
|
||||
when E is void:
|
||||
Result[T1, void].err()
|
||||
else:
|
||||
Result[T1, E].err(self.e)
|
||||
|
||||
func mapErr*[T: not void, E, A](
|
||||
self: Result[T, E], f: proc(x: E): A): Result[T, A] {.inline.} =
|
||||
func flatMap*[E, T1](
|
||||
self: Result[void, E], f: proc(): Result[T1, E]): Result[T1, E] {.inline.} =
|
||||
if self.o: f()
|
||||
else:
|
||||
when E is void:
|
||||
Result[T1, void].err()
|
||||
else:
|
||||
Result[T1, E].err(self.e)
|
||||
|
||||
func mapErr*[T, E0, E1](
|
||||
self: Result[T, E0], f: proc(x: E0): E1): Result[T, E1] {.inline.} =
|
||||
## Transform error using f, or leave untouched
|
||||
if self.o:
|
||||
when T is void:
|
||||
result.ok()
|
||||
else:
|
||||
result.ok(self.v)
|
||||
else:
|
||||
result.err(f(self.e))
|
||||
|
||||
func mapErr*[T, E1](
|
||||
self: Result[T, void], f: proc(): E1): Result[T, E1] {.inline.} =
|
||||
## Transform error using f, or return value
|
||||
if self.o: result.ok(self.v)
|
||||
else: result.err(f(self.e))
|
||||
if self.o:
|
||||
when T is void:
|
||||
result.ok()
|
||||
else:
|
||||
result.ok(self.v)
|
||||
else:
|
||||
result.err(f())
|
||||
|
||||
func mapConvert*[T0, E0](
|
||||
self: Result[T0, E0], T1: type): Result[T1, E0] {.inline.} =
|
||||
func mapErr*[T, E0](
|
||||
self: Result[T, E0], f: proc(x: E0)): Result[T, void] {.inline.} =
|
||||
## Transform error using f, or return value
|
||||
if self.o:
|
||||
when T is void:
|
||||
result.ok()
|
||||
else:
|
||||
result.ok(self.v)
|
||||
else:
|
||||
f(self.e)
|
||||
result.err()
|
||||
|
||||
func mapErr*[T](
|
||||
self: Result[T, void], f: proc()): Result[T, void] {.inline.} =
|
||||
## Transform error using f, or return value
|
||||
if self.o:
|
||||
when T is void:
|
||||
result.ok()
|
||||
else:
|
||||
result.ok(self.v)
|
||||
else:
|
||||
f()
|
||||
result.err()
|
||||
|
||||
func mapConvert*[T0, E](
|
||||
self: Result[T0, E], T1: type): Result[T1, E] {.inline.} =
|
||||
## Convert result value to A using an conversion
|
||||
# Would be nice if it was automatic...
|
||||
if self.o: result.ok(T1(self.v))
|
||||
else: result.err(self.e)
|
||||
if self.o:
|
||||
when T1 is void:
|
||||
result.ok()
|
||||
else:
|
||||
result.ok(T1(self.v))
|
||||
else:
|
||||
when E is void:
|
||||
result.err()
|
||||
else:
|
||||
result.err(self.e)
|
||||
|
||||
func mapCast*[T0, E0](
|
||||
self: Result[T0, E0], T1: type): Result[T1, E0] {.inline.} =
|
||||
func mapCast*[T0, E](
|
||||
self: Result[T0, E], T1: type): Result[T1, E] {.inline.} =
|
||||
## Convert result value to A using a cast
|
||||
## Would be nice with nicer syntax...
|
||||
if self.o: result.ok(cast[T1](self.v))
|
||||
else: result.err(self.e)
|
||||
else:
|
||||
when E is void:
|
||||
result.err()
|
||||
else:
|
||||
result.err(self.e)
|
||||
|
||||
template `and`*[T0, E, T1](self: Result[T0, E], other: Result[T1, E]): Result[T1, E] =
|
||||
## Evaluate `other` iff self.isOk, else return error
|
||||
## fail-fast - will not evaluate other if a is an error
|
||||
let s = self
|
||||
let s = (self) # TODO avoid copy
|
||||
if s.o:
|
||||
other
|
||||
else:
|
||||
|
@ -388,7 +571,10 @@ template `and`*[T0, E, T1](self: Result[T0, E], other: Result[T1, E]): Result[T1
|
|||
s
|
||||
else:
|
||||
type R = type(other)
|
||||
err(R, s.e)
|
||||
when E is void:
|
||||
err(R)
|
||||
else:
|
||||
err(R, s.e)
|
||||
|
||||
template `or`*[T, E0, E1](self: Result[T, E0], other: Result[T, E1]): Result[T, E1] =
|
||||
## Evaluate `other` iff `not self.isOk`, else return `self`
|
||||
|
@ -396,18 +582,45 @@ template `or`*[T, E0, E1](self: Result[T, E0], other: Result[T, E1]): Result[T,
|
|||
##
|
||||
## ```
|
||||
## func f(): Result[int, SomeEnum] =
|
||||
## f2() or err(EnumValue) # Collapse errors from other module / function
|
||||
## f2() or err(SomeEnum.V) # Collapse errors from other module / function
|
||||
## ```
|
||||
let s = self
|
||||
let s = (self) # TODO avoid copy
|
||||
if s.o:
|
||||
when type(self) is type(other):
|
||||
s
|
||||
else:
|
||||
type R = type(other)
|
||||
ok(R, s.v)
|
||||
when T is void:
|
||||
ok(R)
|
||||
else:
|
||||
ok(R, s.v)
|
||||
else:
|
||||
other
|
||||
|
||||
template orErr*[T, E0, E1](self: Result[T, E0], error: E1): Result[T, E1] =
|
||||
## Evaluate `other` iff `not self.isOk`, else return `self`
|
||||
## fail-fast - will not evaluate `error` if `self` is ok
|
||||
##
|
||||
## ```
|
||||
## func f(): Result[int, SomeEnum] =
|
||||
## f2().orErr(SomeEnum.V) # Collapse errors from other module / function
|
||||
## ```
|
||||
##
|
||||
## ** Experimental, may be removed **
|
||||
let s = (self) # TODO avoid copy
|
||||
type R = Result[T, E1]
|
||||
if s.o:
|
||||
when type(self) is R:
|
||||
s
|
||||
else:
|
||||
when T is void:
|
||||
ok(R)
|
||||
else:
|
||||
ok(R, s.v)
|
||||
else:
|
||||
err(R, error)
|
||||
|
||||
|
||||
template catch*(body: typed): Result[type(body), ref CatchableError] =
|
||||
## Catch exceptions for body and store them in the Result
|
||||
##
|
||||
|
@ -442,7 +655,10 @@ template capture*[E: Exception](T: type, someExceptionExpr: ref E): Result[T, re
|
|||
ret = R.err(caught)
|
||||
ret
|
||||
|
||||
func `==`*[T0: not void, E0, T1: not void, E1](lhs: Result[T0, E0], rhs: Result[T1, E1]): bool {.inline.} =
|
||||
func `==`*[
|
||||
T0: not void, E0: not void,
|
||||
T1: not void, E1: not void](
|
||||
lhs: Result[T0, E0], rhs: Result[T1, E1]): bool {.inline.} =
|
||||
if lhs.o != rhs.o:
|
||||
false
|
||||
elif lhs.o: # and rhs.o implied
|
||||
|
@ -450,27 +666,39 @@ func `==`*[T0: not void, E0, T1: not void, E1](lhs: Result[T0, E0], rhs: Result[
|
|||
else:
|
||||
lhs.e == rhs.e
|
||||
|
||||
func `==`*[E0, E1](lhs: Result[void, E0], rhs: Result[void, E1]): bool {.inline.} =
|
||||
func `==`*[E0, E1](
|
||||
lhs: Result[void, E0], rhs: Result[void, E1]): bool {.inline.} =
|
||||
if lhs.o != rhs.o:
|
||||
false
|
||||
elif lhs.o:
|
||||
elif lhs.o: # and rhs.o implied
|
||||
true
|
||||
else:
|
||||
lhs.e == rhs.e
|
||||
|
||||
func get*[T: not void, E](self: Result[T, E]): T {.inline.} =
|
||||
func `==`*[T0, T1](
|
||||
lhs: Result[T0, void], rhs: Result[T1, void]): bool {.inline.} =
|
||||
if lhs.o != rhs.o:
|
||||
false
|
||||
elif lhs.o: # and rhs.o implied
|
||||
lhs.v == rhs.v
|
||||
else:
|
||||
true
|
||||
|
||||
func get*[T, E](self: Result[T, E]): T {.inline.} =
|
||||
## Fetch value of result if set, or raise Defect
|
||||
## Exception bridge mode: raise given Exception instead
|
||||
## See also: Option.get
|
||||
assertOk(self)
|
||||
self.v
|
||||
when T isnot void:
|
||||
self.v
|
||||
|
||||
func tryGet*[T: not void, E](self: Result[T, E]): T {.inline.} =
|
||||
func tryGet*[T, E](self: Result[T, E]): T {.inline.} =
|
||||
## Fetch value of result if set, or raise
|
||||
## When E is an Exception, raise that exception - otherwise, raise a ResultError[E]
|
||||
mixin raiseResultError
|
||||
if not self.o: self.raiseResultError()
|
||||
self.v
|
||||
when T isnot void:
|
||||
self.v
|
||||
|
||||
func get*[T, E](self: Result[T, E], otherwise: T): T {.inline.} =
|
||||
## Fetch value of result if set, or return the value `otherwise`
|
||||
|
@ -479,33 +707,34 @@ func get*[T, E](self: Result[T, E], otherwise: T): T {.inline.} =
|
|||
if self.o: self.v
|
||||
else: otherwise
|
||||
|
||||
func get*[T, E](self: var Result[T, E]): var T {.inline.} =
|
||||
func get*[T: not void, E](self: var Result[T, E]): var T {.inline.} =
|
||||
## Fetch value of result if set, or raise Defect
|
||||
## Exception bridge mode: raise given Exception instead
|
||||
## See also: Option.get
|
||||
assertOk(self)
|
||||
self.v
|
||||
|
||||
template `[]`*[T: not void, E](self: Result[T, E]): T =
|
||||
template `[]`*[T, E](self: Result[T, E]): T =
|
||||
## Fetch value of result if set, or raise Defect
|
||||
## Exception bridge mode: raise given Exception instead
|
||||
mixin get
|
||||
self.get()
|
||||
|
||||
template `[]`*[T, E](self: var Result[T, E]): var T =
|
||||
template `[]`*[T: not void, E](self: var Result[T, E]): var T =
|
||||
## Fetch value of result if set, or raise Defect
|
||||
## Exception bridge mode: raise given Exception instead
|
||||
mixin get
|
||||
self.get()
|
||||
|
||||
template unsafeGet*[T, E](self: Result[T, E]): T =
|
||||
template unsafeGet*[T: not void, E](self: Result[T, E]): T =
|
||||
## Fetch value of result if set, undefined behavior if unset
|
||||
## See also: Option.unsafeGet
|
||||
assert self.o
|
||||
|
||||
## See also: `unsafeError`
|
||||
self.v
|
||||
|
||||
func expect*[T: not void, E](self: Result[T, E], m: string): T =
|
||||
template unsafeGet*[E](self: Result[void, E]) =
|
||||
## Fetch value of result if set, undefined behavior if unset
|
||||
## See also: `unsafeError`
|
||||
assert self.o
|
||||
|
||||
func expect*[T, E](self: Result[T, E], m: string): T =
|
||||
## Return value of Result, or raise a `Defect` with the given message - use
|
||||
## this helper to extract the value when an error is not expected, for example
|
||||
## because the program logic dictates that the operation should never fail
|
||||
|
@ -520,7 +749,8 @@ func expect*[T: not void, E](self: Result[T, E], m: string): T =
|
|||
raiseResultDefect(m, self.e)
|
||||
else:
|
||||
raiseResultDefect(m)
|
||||
self.v
|
||||
when T isnot void:
|
||||
self.v
|
||||
|
||||
func expect*[T: not void, E](self: var Result[T, E], m: string): var T =
|
||||
if not self.o:
|
||||
|
@ -530,10 +760,14 @@ func expect*[T: not void, E](self: var Result[T, E], m: string): var T =
|
|||
raiseResultDefect(m)
|
||||
self.v
|
||||
|
||||
func `$`*(self: Result): string =
|
||||
func `$`*[T, E](self: Result[T, E]): string =
|
||||
## Returns string representation of `self`
|
||||
if self.o: "Ok(" & $self.v & ")"
|
||||
else: "Err(" & $self.e & ")"
|
||||
if self.o:
|
||||
when T is void: "ok()"
|
||||
else: "ok(" & $self.v & ")"
|
||||
else:
|
||||
when E is void: "err()"
|
||||
else: "err(" & $self.e & ")"
|
||||
|
||||
func error*[T, E](self: Result[T, E]): E =
|
||||
## Fetch error of result if set, or raise Defect
|
||||
|
@ -542,111 +776,112 @@ func error*[T, E](self: Result[T, E]): E =
|
|||
raiseResultDefect("Trying to access error when value is set", self.v)
|
||||
else:
|
||||
raiseResultDefect("Trying to access error when value is set")
|
||||
when E isnot void:
|
||||
self.e
|
||||
|
||||
func tryError*[T, E](self: Result[T, E]): E {.inline.} =
|
||||
## Fetch error of result if set, or raise
|
||||
## Raises a ResultError[T]
|
||||
mixin raiseResultOk
|
||||
if self.o: self.raiseResultOk()
|
||||
when E isnot void:
|
||||
self.e
|
||||
|
||||
template unsafeError*[T, E: not void](self: Result[T, E]): E =
|
||||
## Fetch value of result if set, undefined behavior if unset
|
||||
## See also: `unsafeGet`
|
||||
self.e
|
||||
|
||||
template value*[T, E](self: Result[T, E]): T =
|
||||
mixin get
|
||||
self.get()
|
||||
template unsafeError*[T](self: Result[T, void]) =
|
||||
## Fetch value of result if set, undefined behavior if unset
|
||||
## See also: `unsafeGet`
|
||||
assert not self.o # Emulate field access defect in debug builds
|
||||
|
||||
template value*[T, E](self: var Result[T, E]): T =
|
||||
mixin get
|
||||
self.get()
|
||||
# Alternative spellings for get
|
||||
template value*[T, E](self: Result[T, E]): T = self.get()
|
||||
template value*[T: not void, E](self: var Result[T, E]): var T = self.get()
|
||||
|
||||
template valueOr*[T, E](self: Result[T, E], def: T): T =
|
||||
## Fetch value of result if set, or supplied default
|
||||
## default will not be evaluated iff value is set
|
||||
if self.o: self.v
|
||||
template valueOr*[T: not void, E](self: Result[T, E], def: untyped): T =
|
||||
## Fetch value of result if set, or evaluate `def`
|
||||
## `def` is evaluated lazily, and must be an expression of `T` or exit
|
||||
## the scope (for example using `return` / `raise`)
|
||||
##
|
||||
## Example:
|
||||
## ```
|
||||
## let
|
||||
## v = Result[int, string].err("hello")
|
||||
## x = v.valueOr: 42 # x == 42 now
|
||||
## y = v.valueOr: raise (ref ValueError)(msg: "v is an error, gasp!")
|
||||
## ```
|
||||
let s = (self) # TODO avoid copy
|
||||
if s.o: s.v
|
||||
else: def
|
||||
|
||||
# void support
|
||||
template errorOr*[T: not void, E](self: Result[T, E], def: untyped): E =
|
||||
## Fetch error of result if not set, or evaluate `def`
|
||||
## `def` is evaluated lazily, and must be an expression of `T` or exit
|
||||
## the scope (for example using `return` / `raise`)
|
||||
let s = (self) # TODO avoid copy
|
||||
if not s.o: s.e
|
||||
else: def
|
||||
|
||||
template ok*[E](R: type Result[void, E]): auto =
|
||||
## Initialize a result with a success and value
|
||||
## Example: `Result[int, string].ok(42)`
|
||||
R(o: true)
|
||||
|
||||
template ok*[E](self: var Result[void, E]) =
|
||||
## Set the result to success and update value
|
||||
## Example: `result.ok(42)`
|
||||
mixin ok
|
||||
self = (type self).ok()
|
||||
|
||||
template ok*(): auto =
|
||||
mixin ok
|
||||
ok(typeof(result))
|
||||
|
||||
template err*(): auto =
|
||||
mixin err
|
||||
err(typeof(result))
|
||||
|
||||
# TODO:
|
||||
# Supporting `map` and `get` operations on a `void` result is quite
|
||||
# an unusual API. We should provide some motivating examples.
|
||||
|
||||
func map*[E, A](
|
||||
self: Result[void, E], f: proc(): A): Result[A, E] {.inline.} =
|
||||
## Transform value using f, or return error
|
||||
if self.o: result.ok(f())
|
||||
else: result.err(self.e)
|
||||
|
||||
func flatMap*[E, A](
|
||||
self: Result[void, E], f: proc(): Result[A, E]): Result[A, E] {.inline.} =
|
||||
if self.o: f(self.v)
|
||||
else: Result[A, E].err(self.e)
|
||||
|
||||
func mapErr*[E, A](
|
||||
self: Result[void, E], f: proc(x: E): A): Result[void, A] {.inline.} =
|
||||
## Transform error using f, or return value
|
||||
if self.o: result.ok()
|
||||
else: result.err(f(self.e))
|
||||
|
||||
func map*[T, E](
|
||||
self: Result[T, E], f: proc(x: T)): Result[void, E] {.inline.} =
|
||||
## Transform value using f, or return error
|
||||
if self.o: f(self.v); result.ok()
|
||||
else: result.err(self.e)
|
||||
|
||||
func get*[E](self: Result[void, E]) {.inline.} =
|
||||
## Fetch value of result if set, or raise
|
||||
## See also: Option.get
|
||||
mixin assertOk
|
||||
assertOk(self)
|
||||
|
||||
func tryGet*[E](self: Result[void, E]) {.inline.} =
|
||||
## Fetch value of result if set, or raise a CatchableError
|
||||
mixin raiseResultError
|
||||
if not self.o:
|
||||
self.raiseResultError()
|
||||
|
||||
template `[]`*[E](self: Result[void, E]) =
|
||||
## Fetch value of result if set, or raise
|
||||
mixin get
|
||||
self.get()
|
||||
|
||||
template unsafeGet*[E](self: Result[void, E]) =
|
||||
## Fetch value of result if set, undefined behavior if unset
|
||||
## See also: Option.unsafeGet
|
||||
assert self.o
|
||||
|
||||
func expect*[E](self: Result[void, E], msg: string) =
|
||||
if not self.o:
|
||||
when E isnot void:
|
||||
raiseResultDefect(msg, self.e)
|
||||
func flatten*[T, E](self: Result[Result[T, E], E]): Result[T, E] =
|
||||
## Remove one level of nesting
|
||||
if self.o:
|
||||
self.v
|
||||
else:
|
||||
when E is void:
|
||||
err(Result[T, E])
|
||||
else:
|
||||
raiseResultDefect(msg)
|
||||
err(Result[T, E], self.error)
|
||||
|
||||
func `$`*[E](self: Result[void, E]): string =
|
||||
## Returns string representation of `self`
|
||||
if self.o: "Ok()"
|
||||
else: "Err(" & $self.e & ")"
|
||||
func filter*[T, E](
|
||||
self: Result[T, E],
|
||||
callback: proc(x: T): Result[void, E]): Result[T, E] =
|
||||
## Apply `callback` to the `self`, iff `self` is not an error. If `callback`
|
||||
## returns an error, return that error, else return `self`
|
||||
|
||||
template value*[E](self: Result[void, E]) =
|
||||
mixin get
|
||||
self.get()
|
||||
if self.o:
|
||||
callback(self.v) and self
|
||||
else:
|
||||
self
|
||||
|
||||
template value*[E](self: var Result[void, E]) =
|
||||
mixin get
|
||||
self.get()
|
||||
func filter*[E](
|
||||
self: Result[void, E],
|
||||
callback: proc(): Result[void, E]): Result[void, E] =
|
||||
## Apply `callback` to the `self`, iff `self` is not an error. If `callback`
|
||||
## returns an error, return that error, else return `self`
|
||||
|
||||
if self.o:
|
||||
callback() and self
|
||||
else:
|
||||
self
|
||||
|
||||
func filter*[T](
|
||||
self: Result[T, void],
|
||||
callback: proc(x: T): bool): Result[T, void] =
|
||||
## Apply `callback` to the `self`, iff `self` is not an error. If `callback`
|
||||
## returns an error, return that error, else return `self`
|
||||
|
||||
if self.o:
|
||||
if callback(self.v):
|
||||
self
|
||||
else:
|
||||
Result[T, void].err()
|
||||
else:
|
||||
self
|
||||
|
||||
# Options compatibility
|
||||
|
||||
template isSome*(o: Opt): bool =
|
||||
## Alias for `isOk`
|
||||
isOk o
|
||||
|
||||
template isNone*(o: Opt): bool =
|
||||
## Alias of `isErr`
|
||||
isErr o
|
||||
|
||||
# Syntactic convenience
|
||||
|
||||
template `?`*[T, E](self: Result[T, E]): auto =
|
||||
## Early return - if self is an error, we will return from the current
|
||||
|
@ -664,7 +899,10 @@ template `?`*[T, E](self: Result[T, E]): auto =
|
|||
when typeof(result) is typeof(v):
|
||||
return v
|
||||
else:
|
||||
return err(typeof(result), v.e)
|
||||
when E is void:
|
||||
return err(typeof(result))
|
||||
else:
|
||||
return err(typeof(result), v.e)
|
||||
|
||||
when not(T is void):
|
||||
v.v
|
||||
|
|
|
@ -1,197 +1,180 @@
|
|||
# nim-result is also available stand-alone from https://github.com/arnetheduck/nim-result/
|
||||
|
||||
import ../stew/results
|
||||
|
||||
type R = Result[int, string]
|
||||
|
||||
# Basic usage, producer
|
||||
func works(): R = R.ok(42)
|
||||
func works2(): R = result.ok(42)
|
||||
func fails(): R = R.err("dummy")
|
||||
func fails2(): R = result.err("dummy")
|
||||
|
||||
func raises(): int =
|
||||
raise (ref CatchableError)(msg: "hello")
|
||||
block:
|
||||
func works(): R = R.ok(42)
|
||||
func works2(): R = result.ok(42)
|
||||
func works3(): R = ok(42)
|
||||
|
||||
# Basic usage, consumer
|
||||
let
|
||||
rOk = works()
|
||||
rOk2 = works2()
|
||||
rErr = fails()
|
||||
rErr2 = fails2()
|
||||
func fails(): R = R.err("dummy")
|
||||
func fails2(): R = result.err("dummy")
|
||||
func fails3(): R = err("dummy")
|
||||
|
||||
doAssert rOk.isOk
|
||||
doAssert rOk2.isOk
|
||||
doAssert rOk.get() == 42
|
||||
doAssert (not rOk.isErr)
|
||||
doAssert rErr.isErr
|
||||
doAssert rErr2.isErr
|
||||
let
|
||||
rOk = works()
|
||||
rOk2 = works2()
|
||||
rOk3 = works3()
|
||||
|
||||
# Combine
|
||||
doAssert (rOk and rErr).isErr
|
||||
doAssert (rErr and rOk).isErr
|
||||
doAssert (rOk or rErr).isOk
|
||||
doAssert (rErr or rOk).isOk
|
||||
rErr = fails()
|
||||
rErr2 = fails2()
|
||||
rErr3 = fails3()
|
||||
|
||||
# `and` heterogenous types
|
||||
doAssert (rOk and rOk.map(proc(x: auto): auto = $x))[] == $(rOk[])
|
||||
doAssert rOk.isOk
|
||||
doAssert rOk2.isOk
|
||||
doAssert rOk3.isOk
|
||||
doAssert (not rOk.isErr)
|
||||
|
||||
# `or` heterogenous types
|
||||
doAssert (rErr or rErr.mapErr(proc(x: auto): auto = len(x))).error == len(rErr.error)
|
||||
doAssert rErr.isErr
|
||||
doAssert rErr2.isErr
|
||||
doAssert rErr3.isErr
|
||||
|
||||
# Exception on access
|
||||
let va = try: discard rOk.error; false except: true
|
||||
doAssert va, "not an error, should raise"
|
||||
# Mutate
|
||||
var x = rOk
|
||||
x.err("failed now")
|
||||
doAssert x.isErr
|
||||
doAssert x.error == "failed now"
|
||||
|
||||
# Exception on access
|
||||
let vb = try: discard rErr.value; false except: true
|
||||
doAssert vb, "not an value, should raise"
|
||||
# Combine
|
||||
doAssert (rOk and rErr).isErr
|
||||
doAssert (rErr and rOk).isErr
|
||||
doAssert (rOk or rErr).isOk
|
||||
doAssert (rErr or rOk).isOk
|
||||
|
||||
var x = rOk
|
||||
# Fail fast
|
||||
proc failFast(): int = raiseAssert "shouldn't evaluate"
|
||||
proc failFastR(): R = raiseAssert "shouldn't evaluate"
|
||||
|
||||
# Mutate
|
||||
x.err("failed now")
|
||||
doAssert (rErr and failFastR()).isErr
|
||||
doAssert (rOk or failFastR()).isOk
|
||||
|
||||
doAssert x.isErr
|
||||
# `and` heterogenous types
|
||||
doAssert (rOk and Result[string, string].ok($rOk.get())).get() == $(rOk[])
|
||||
|
||||
# Exceptions -> results
|
||||
let c = catch:
|
||||
raises()
|
||||
# `or` heterogenous types
|
||||
doAssert (rErr or Result[int, int].err(len(rErr.error))).error == len(rErr.error)
|
||||
|
||||
doAssert c.isErr
|
||||
# Exception on access
|
||||
doAssert (try: (discard rOk.tryError(); false) except ResultError[int]: true)
|
||||
doAssert (try: (discard rErr.tryGet(); false) except ResultError[string]: true)
|
||||
|
||||
# De-reference
|
||||
try:
|
||||
echo rErr[]
|
||||
doAssert false
|
||||
except:
|
||||
discard
|
||||
# Value access or default
|
||||
doAssert rOk.get(100) == rOk.get()
|
||||
doAssert rErr.get(100) == 100
|
||||
|
||||
doAssert rOk.valueOr(50) == rOk.value
|
||||
doAssert rErr.valueOr(50) == 50
|
||||
doAssert rOk.get() == rOk.unsafeGet()
|
||||
|
||||
# Comparisons
|
||||
doAssert (works() == works2())
|
||||
doAssert (fails() == fails2())
|
||||
doAssert (works() != fails())
|
||||
doAssert rOk.valueOr(failFast()) == rOk.value()
|
||||
let rErrV = rErr.valueOr: 100
|
||||
doAssert rErrV == 100
|
||||
|
||||
var counter = 0
|
||||
proc incCounter(): R =
|
||||
counter += 1
|
||||
R.ok(counter)
|
||||
let rOkV = rOk.errorOr: "quack"
|
||||
doAssert rOkV == "quack"
|
||||
|
||||
doAssert (rErr and incCounter()).isErr, "b fails"
|
||||
doAssert counter == 0, "should fail fast on rErr"
|
||||
# Exceptions -> results
|
||||
func raises(): int =
|
||||
raise (ref CatchableError)(msg: "hello")
|
||||
|
||||
# Mapping
|
||||
doAssert (rOk.map(func(x: int): string = $x)[] == $rOk.value)
|
||||
doAssert (rOk.flatMap(
|
||||
proc(x: int): Result[string, string] = Result[string, string].ok($x))[] == $rOk.value)
|
||||
doAssert (rErr.mapErr(func(x: string): string = x & "no!").error == (rErr.error & "no!"))
|
||||
let c = catch:
|
||||
raises()
|
||||
doAssert c.isErr
|
||||
|
||||
# Exception interop
|
||||
let e = capture(int, (ref ValueError)(msg: "test"))
|
||||
doAssert e.isErr
|
||||
doAssert e.error.msg == "test"
|
||||
# De-reference
|
||||
try:
|
||||
echo rErr[]
|
||||
doAssert false
|
||||
except:
|
||||
discard
|
||||
|
||||
try:
|
||||
discard e.tryGet
|
||||
doAssert false, "should have raised"
|
||||
except ValueError as e:
|
||||
doAssert e.msg == "test"
|
||||
# Comparisons
|
||||
doAssert (rOk == rOk)
|
||||
doAssert (rErr == rErr)
|
||||
doAssert (rOk != rErr)
|
||||
|
||||
# Nice way to checks
|
||||
if (let v = works(); v.isOk):
|
||||
doAssert v[] == v.value
|
||||
# Mapping
|
||||
doAssert (rOk.map(func(x: int): string = $x)[] == $rOk.value)
|
||||
doAssert (rOk.map(func(x: int) = discard)).isOk()
|
||||
|
||||
# Can formalise it into a template (https://github.com/arnetheduck/nim-result/issues/8)
|
||||
template `?=`*(v: untyped{nkIdent}, vv: Result): bool =
|
||||
(let vr = vv; template v: auto {.used.} = unsafeGet(vr); vr.isOk)
|
||||
if f ?= works():
|
||||
doAssert f == works().value
|
||||
doAssert (rOk.flatMap(
|
||||
proc(x: int): Result[string, string] = Result[string, string].ok($x))[] == $rOk.value)
|
||||
|
||||
doAssert $rOk == "Ok(42)"
|
||||
doAssert (rErr.mapErr(func(x: string): string = x & "no!").error == (rErr.error & "no!"))
|
||||
|
||||
doAssert rOk.mapConvert(int64)[] == int64(42)
|
||||
doAssert rOk.mapCast(int8)[] == int8(42)
|
||||
doAssert rOk.mapConvert(uint64)[] == uint64(42)
|
||||
# Casts and conversions
|
||||
doAssert rOk.mapConvert(int64)[] == int64(42)
|
||||
doAssert rOk.mapConvert(uint64)[] == uint64(42)
|
||||
doAssert rOk.mapCast(int8)[] == int8(42)
|
||||
|
||||
try:
|
||||
discard rErr.get()
|
||||
doAssert false
|
||||
except Defect: # TODO catching defects is undefined behaviour, use external test suite?
|
||||
discard
|
||||
doAssert (rErr.orErr(32)).error == 32
|
||||
doAssert (rOk.orErr(failFast())).get() == rOk.get()
|
||||
|
||||
try:
|
||||
discard rOk.error()
|
||||
doAssert false
|
||||
except Defect: # TODO catching defects is undefined behaviour, use external test suite?
|
||||
discard
|
||||
# string conversion
|
||||
doAssert $rOk == "ok(42)"
|
||||
doAssert $rErr == "err(dummy)"
|
||||
|
||||
# TODO there's a bunch of operators that one could lift through magic - this
|
||||
# is mainly an example
|
||||
template `+`*(self, other: Result): untyped =
|
||||
## Perform `+` on the values of self and other, if both are ok
|
||||
type R = type(other)
|
||||
if self.isOk:
|
||||
if other.isOk:
|
||||
R.ok(self.value + other.value)
|
||||
else:
|
||||
R.err(other.error)
|
||||
else:
|
||||
R.err(self.error)
|
||||
# Exception interop
|
||||
let e = capture(int, (ref ValueError)(msg: "test"))
|
||||
doAssert e.isErr
|
||||
doAssert e.error.msg == "test"
|
||||
|
||||
# Simple lifting..
|
||||
doAssert (rOk + rOk)[] == rOk.value + rOk.value
|
||||
try:
|
||||
discard rOk.tryError()
|
||||
doAssert false, "should have raised"
|
||||
except ValueError:
|
||||
discard
|
||||
|
||||
iterator items[T, E](self: Result[T, E]): T =
|
||||
## Iterate over result as if it were a collection of either 0 or 1 items
|
||||
## TODO should a Result[seq[X]] iterate over items in seq? there are
|
||||
## arguments for and against
|
||||
if self.isOk:
|
||||
yield self.value
|
||||
try:
|
||||
discard e.tryGet()
|
||||
doAssert false, "should have raised"
|
||||
except ValueError as e:
|
||||
doAssert e.msg == "test"
|
||||
|
||||
# Iteration
|
||||
var counter2 = 0
|
||||
for v in rOk:
|
||||
counter2 += 1
|
||||
# Nice way to checks
|
||||
if (let v = works(); v.isOk):
|
||||
doAssert v[] == v.value
|
||||
|
||||
doAssert counter2 == 1, "one-item collection when set"
|
||||
# Expectations
|
||||
doAssert rOk.expect("testOk never fails") == 42
|
||||
|
||||
func testOk(): Result[int, string] =
|
||||
ok 42
|
||||
# Question mark operator
|
||||
func testQn(): Result[int, string] =
|
||||
let x = ?works() - ?works()
|
||||
ok(x)
|
||||
|
||||
func testErr(): Result[int, string] =
|
||||
err "323"
|
||||
func testQn2(): Result[int, string] =
|
||||
# looks like we can even use it creatively like this
|
||||
if ?fails() == 42: raise (ref ValueError)(msg: "shouldn't happen")
|
||||
|
||||
doAssert testOk()[] == 42
|
||||
doAssert testErr().error == "323"
|
||||
func testQn3(): Result[bool, string] =
|
||||
# different T but same E
|
||||
let x = ?works() - ?works()
|
||||
ok(x == 0)
|
||||
|
||||
doAssert testOk().expect("testOk never fails") == 42
|
||||
doAssert testQn()[] == 0
|
||||
doAssert testQn2().isErr
|
||||
doAssert testQn3()[]
|
||||
|
||||
func testQn(): Result[int, string] =
|
||||
let x = ?works() - ?works()
|
||||
result.ok(x)
|
||||
proc heterOr(): Result[int, int] =
|
||||
let value = ? (rErr or err(42)) # TODO ? binds more tightly than `or` - can that be fixed?
|
||||
doAssert value + 1 == value, "won't reach, ? will shortcut execution"
|
||||
ok(value)
|
||||
|
||||
func testQn2(): Result[int, string] =
|
||||
# looks like we can even use it creatively like this
|
||||
if ?fails() == 42: raise (ref ValueError)(msg: "shouldn't happen")
|
||||
doAssert heterOr().error() == 42
|
||||
|
||||
func testQn3(): Result[bool, string] =
|
||||
# different T but same E
|
||||
let x = ?works() - ?works()
|
||||
result.ok(x == 0)
|
||||
# Flatten
|
||||
doAssert Result[R, string].ok(rOk).flatten() == rOk
|
||||
doAssert Result[R, string].ok(rErr).flatten() == rErr
|
||||
|
||||
doAssert testQn()[] == 0
|
||||
doAssert testQn2().isErr
|
||||
doAssert testQn3()[]
|
||||
|
||||
proc heterOr(): Result[int, int] =
|
||||
let value = ? (rErr or err(42)) # TODO ? binds more tightly than `or` - can that be fixed?
|
||||
doAssert value + 1 == value, "won't reach, ? will shortcut execution"
|
||||
ok(value)
|
||||
|
||||
doAssert heterOr().error() == 42
|
||||
# Filter
|
||||
doAssert rOk.filter(proc(x: int): auto = Result[void, string].ok()) == rOk
|
||||
doAssert rOk.filter(proc(x: int): auto = Result[void, string].err("filter")).error == "filter"
|
||||
doAssert rErr.filter(proc(x: int): auto = Result[void, string].err("filter")) == rErr
|
||||
|
||||
# Exception conversions - toException must not be inside a block
|
||||
type
|
||||
AnEnum = enum
|
||||
anEnumA
|
||||
|
@ -224,68 +207,192 @@ func testToString(): int =
|
|||
|
||||
doAssert testToString() == 42
|
||||
|
||||
type VoidRes = Result[void, int]
|
||||
block: # Result[void, E]
|
||||
type VoidRes = Result[void, int]
|
||||
|
||||
func worksVoid(): VoidRes = VoidRes.ok()
|
||||
func worksVoid2(): VoidRes = result.ok()
|
||||
func failsVoid(): VoidRes = VoidRes.err(42)
|
||||
func failsVoid2(): VoidRes = result.err(42)
|
||||
func worksVoid(): VoidRes = VoidRes.ok()
|
||||
func worksVoid2(): VoidRes = result.ok()
|
||||
func worksVoid3(): VoidRes = ok()
|
||||
|
||||
let
|
||||
vOk = worksVoid()
|
||||
vOk2 = worksVoid2()
|
||||
vErr = failsVoid()
|
||||
vErr2 = failsVoid2()
|
||||
func failsVoid(): VoidRes = VoidRes.err(42)
|
||||
func failsVoid2(): VoidRes = result.err(42)
|
||||
func failsVoid3(): VoidRes = err(42)
|
||||
|
||||
doAssert vOk.isOk
|
||||
doAssert vOk2.isOk
|
||||
doAssert vErr.isErr
|
||||
doAssert vErr2.isErr
|
||||
let
|
||||
vOk = worksVoid()
|
||||
vOk2 = worksVoid2()
|
||||
vOk3 = worksVoid3()
|
||||
|
||||
vOk.get()
|
||||
vOk.expect("should never fail")
|
||||
vErr = failsVoid()
|
||||
vErr2 = failsVoid2()
|
||||
vErr3 = failsVoid3()
|
||||
|
||||
doAssert vOk.map(proc (): int = 42).get() == 42
|
||||
doAssert vOk.isOk
|
||||
doAssert vOk2.isOk
|
||||
doAssert vOk3.isOk
|
||||
doAssert (not vOk.isErr)
|
||||
|
||||
rOk.map(proc(x: int) = discard).get()
|
||||
doAssert vErr.isErr
|
||||
doAssert vErr2.isErr
|
||||
doAssert vErr3.isErr
|
||||
|
||||
try:
|
||||
rErr.map(proc(x: int) = discard).get()
|
||||
doAssert false
|
||||
except:
|
||||
discard
|
||||
vOk.get()
|
||||
vOk.unsafeGet()
|
||||
vOk.expect("should never fail")
|
||||
|
||||
doAssert vErr.mapErr(proc(x: int): int = 10).error() == 10
|
||||
# Comparisons
|
||||
doAssert (vOk == vOk)
|
||||
doAssert (vErr == vErr)
|
||||
doAssert (vOk != vErr)
|
||||
|
||||
func voidF(): VoidRes =
|
||||
ok()
|
||||
# Mapping
|
||||
doAssert vOk.map(proc (): int = 42).get() == 42
|
||||
vOk.map(proc () = discard).get()
|
||||
|
||||
func voidF2(): VoidRes =
|
||||
? voidF()
|
||||
vOk.mapErr(proc(x: int): int = 10).get()
|
||||
vOk.mapErr(proc(x: int) = discard).get()
|
||||
|
||||
ok()
|
||||
doAssert vErr.mapErr(proc(x: int): int = 10).error() == 10
|
||||
|
||||
doAssert voidF2().isOk
|
||||
# string conversion
|
||||
doAssert $vOk == "ok()"
|
||||
doAssert $vErr == "err(42)"
|
||||
|
||||
# Question mark operator
|
||||
func voidF(): VoidRes =
|
||||
ok()
|
||||
|
||||
type CSRes = Result[void, cstring]
|
||||
func voidF2(): Result[int, int] =
|
||||
? voidF()
|
||||
|
||||
func cstringF(s: string): CSRes =
|
||||
when compiles(err(s)):
|
||||
doAssert false
|
||||
ok(42)
|
||||
|
||||
discard cstringF("test")
|
||||
doAssert voidF2().isOk
|
||||
|
||||
# Compare void
|
||||
block:
|
||||
var a, b: Result[void, bool]
|
||||
doAssert a == b
|
||||
# flatten
|
||||
doAssert Result[VoidRes, int].ok(vOk).flatten() == vOk
|
||||
doAssert Result[VoidRes, int].ok(vErr).flatten() == vErr
|
||||
|
||||
a.ok()
|
||||
# Filter
|
||||
doAssert vOk.filter(proc(): auto = Result[void, int].ok()) == vOk
|
||||
doAssert vOk.filter(proc(): auto = Result[void, int].err(100)).error == 100
|
||||
doAssert vErr.filter(proc(): auto = Result[void, int].err(100)) == vErr
|
||||
|
||||
doAssert not (a == b)
|
||||
doAssert not (b == a)
|
||||
block: # Result[T, void] aka `Opt`
|
||||
type OptInt = Result[int, void]
|
||||
|
||||
b.ok()
|
||||
func worksOpt(): OptInt = OptInt.ok(42)
|
||||
func worksOpt2(): OptInt = result.ok(42)
|
||||
func worksOpt3(): OptInt = ok(42)
|
||||
|
||||
func failsOpt(): OptInt = OptInt.err()
|
||||
func failsOpt2(): OptInt = result.err()
|
||||
func failsOpt3(): OptInt = err()
|
||||
|
||||
let
|
||||
oOk = worksOpt()
|
||||
oOk2 = worksOpt2()
|
||||
oOk3 = worksOpt3()
|
||||
|
||||
oErr = failsOpt()
|
||||
oErr2 = failsOpt2()
|
||||
oErr3 = failsOpt3()
|
||||
|
||||
doAssert oOk.isOk
|
||||
doAssert oOk2.isOk
|
||||
doAssert oOk3.isOk
|
||||
doAssert (not oOk.isErr)
|
||||
|
||||
doAssert oErr.isErr
|
||||
doAssert oErr2.isErr
|
||||
doAssert oErr3.isErr
|
||||
|
||||
# Comparisons
|
||||
doAssert (oOk == oOk)
|
||||
doAssert (oErr == oErr)
|
||||
doAssert (oOk != oErr)
|
||||
|
||||
doAssert oOk.get() == oOk.unsafeGet()
|
||||
oErr.error()
|
||||
oErr.unsafeError()
|
||||
|
||||
# Mapping
|
||||
doAssert oOk.map(proc(x: int): string = $x).get() == $oOk.get()
|
||||
oOk.map(proc(x: int) = discard).get()
|
||||
|
||||
doAssert oOk.mapErr(proc(): int = 10).get() == oOk.get()
|
||||
doAssert oOk.mapErr(proc() = discard).get() == oOk.get()
|
||||
|
||||
doAssert oErr.mapErr(proc(): int = 10).error() == 10
|
||||
|
||||
# string conversion
|
||||
doAssert $oOk == "ok(42)"
|
||||
doAssert $oErr == "err()"
|
||||
|
||||
proc optQuestion(): OptInt =
|
||||
let v = ? oOk
|
||||
ok(v)
|
||||
|
||||
doAssert optQuestion().isOk()
|
||||
|
||||
# Flatten
|
||||
doAssert Result[OptInt, void].ok(oOk).flatten() == oOk
|
||||
doAssert Result[OptInt, void].ok(oErr).flatten() == oErr
|
||||
|
||||
# Filter
|
||||
doAssert oOk.filter(proc(x: int): auto = Result[void, void].ok()) == oOk
|
||||
doAssert oOk.filter(proc(x: int): auto = Result[void, void].err()).isErr()
|
||||
doAssert oErr.filter(proc(x: int): auto = Result[void, void].err()) == oErr
|
||||
|
||||
doAssert oOk.filter(proc(x: int): bool = true) == oOk
|
||||
doAssert oOk.filter(proc(x: int): bool = false).isErr()
|
||||
doAssert oErr.filter(proc(x: int): bool = true) == oErr
|
||||
|
||||
block: # `cstring` dangling reference protection
|
||||
type CSRes = Result[void, cstring]
|
||||
|
||||
func cstringF(s: string): CSRes =
|
||||
when compiles(err(s)):
|
||||
doAssert false
|
||||
|
||||
discard cstringF("test")
|
||||
|
||||
block: # Experiments
|
||||
# Can formalise it into a template (https://github.com/arnetheduck/nim-result/issues/8)
|
||||
template `?=`(v: untyped{nkIdent}, vv: Result): bool =
|
||||
(let vr = vv; template v: auto {.used.} = unsafeGet(vr); vr.isOk)
|
||||
|
||||
if f ?= Result[int, string].ok(42):
|
||||
doAssert f == 42
|
||||
|
||||
# TODO there's a bunch of operators that one could lift through magic - this
|
||||
# is mainly an example
|
||||
template `+`(self, other: Result): untyped =
|
||||
## Perform `+` on the values of self and other, if both are ok
|
||||
type R = type(other)
|
||||
if self.isOk:
|
||||
if other.isOk:
|
||||
R.ok(self.value + other.value)
|
||||
else:
|
||||
R.err(other.error)
|
||||
else:
|
||||
R.err(self.error)
|
||||
|
||||
let rOk = Result[int, string].ok(42)
|
||||
# Simple lifting..
|
||||
doAssert (rOk + rOk)[] == rOk.value + rOk.value
|
||||
|
||||
iterator items[T, E](self: Result[T, E]): T =
|
||||
## Iterate over result as if it were a collection of either 0 or 1 items
|
||||
## TODO should a Result[seq[X]] iterate over items in seq? there are
|
||||
## arguments for and against
|
||||
if self.isOk:
|
||||
yield self.value
|
||||
|
||||
# Iteration
|
||||
var counter2 = 0
|
||||
for v in rOk:
|
||||
counter2 += 1
|
||||
|
||||
doAssert counter2 == 1, "one-item collection when set"
|
||||
|
||||
doAssert a == b
|
||||
|
|
Loading…
Reference in New Issue