nim-stew/tests/test_result.nim

242 lines
5.3 KiB
Nim
Raw Normal View History

2020-01-23 16:35:30 +00:00
# nim-result is also available stand-alone from https://github.com/arnetheduck/nim-result/
import ../stew/result
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 newException(Exception, "hello")
# Basic usage, consumer
let
rOk = works()
rOk2 = works2()
rErr = fails()
rErr2 = fails2()
doAssert rOk.isOk
doAssert rOk2.isOk
doAssert rOk.get() == 42
doAssert (not rOk.isErr)
doAssert rErr.isErr
doAssert rErr2.isErr
# Combine
doAssert (rOk and rErr).isErr
doAssert (rErr and rOk).isErr
doAssert (rOk or rErr).isOk
doAssert (rErr or rOk).isOk
# Exception on access
let va = try: discard rOk.error; false except: true
doAssert va, "not an error, should raise"
# Exception on access
let vb = try: discard rErr.value; false except: true
doAssert vb, "not an value, should raise"
var x = rOk
# Mutate
x.err("failed now")
doAssert x.isErr
# Exceptions -> results
let c = catch:
raises()
doAssert c.isErr
# De-reference
try:
echo rErr[]
doAssert false
except:
discard
doAssert rOk.valueOr(50) == rOk.value
doAssert rErr.valueOr(50) == 50
# Comparisons
doAssert (works() == works2())
doAssert (fails() == fails2())
doAssert (works() != fails())
var counter = 0
proc incCounter(): R =
counter += 1
R.ok(counter)
doAssert (rErr and incCounter()).isErr, "b fails"
doAssert counter == 0, "should fail fast on rErr"
# 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!"))
# Exception interop
let e = capture(int, newException(Exception, "test"))
doAssert e.isErr
try:
discard e[]
doAssert false, "should have raised"
except:
doAssert getCurrentException().msg == "test"
# Nice way to checks
if (let v = works(); v.isOk):
doAssert v[] == v.value
# 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 == "Ok(42)"
doAssert rOk.mapConvert(int64)[] == int64(42)
doAssert rOk.mapCast(int8)[] == int8(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)
# 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"
func testOk(): Result[int, string] =
ok 42
func testErr(): Result[int, string] =
err "323"
doAssert testOk()[] == 42
doAssert testErr().error == "323"
# It's also possible to use the same trick for stack capture:
template capture*(): untyped =
type R = type(result)
var ret: R
try:
# TODO is this needed? I think so, in order to grab a call stack, but
# haven't actually tested...
if true:
# I'm sure there's a nicer way - this just works :)
raise newException(Exception, "")
except:
ret = R.err(getCurrentException())
ret
proc testCapture(): Result[int, ref Exception] =
return capture()
doAssert testCapture().isErr
func testQn(): Result[int, string] =
let x = ?works() - ?works()
result.ok(x)
func testQn2(): Result[int, string] =
# looks like we can even use it creatively like this
if ?fails() == 42: raise newException(Exception, "shouldn't happen")
doAssert testQn()[] == 0
doAssert testQn2().isErr
type
AnEnum = enum
anEnumA
anEnumB
AnException = ref object of Exception
v: AnEnum
func toException(v: AnEnum): AnException = AnException(v: v)
func testToException(): int =
try:
var r = Result[int, AnEnum].err(anEnumA)
r[]
except AnException:
42
doAssert testToException() == 42
type
AnEnum2 = enum
anEnum2A
anEnum2B
func testToString(): int =
try:
var r = Result[int, AnEnum2].err(anEnum2A)
r[]
except ResultError[AnEnum2]:
42
doAssert testToString() == 42
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)
let
vOk = worksVoid()
vOk2 = worksVoid2()
vErr = failsVoid()
vErr2 = failsVoid2()
doAssert vOk.isOk
doAssert vOk2.isOk
doAssert vErr.isErr
doAssert vErr2.isErr
vOk.get()
doAssert vOk.map(proc (): int = 42).get() == 42
rOk.map(proc(x: int) = discard).get()
try:
rErr.map(proc(x: int) = discard).get()
doAssert false
except:
discard
doAssert vErr.mapErr(proc(x: int): int = 10).error() == 10