Graduate stew/results -> results (#185)
Since the results package has seen "stable" use for quite a while now, it's as good time a time as any to release 1.0 and migrate it out of stew, thus signalling API stability of some sort. Part of that "stabilization" is underway in the nim-results [issue tracker](https://github.com/arnetheduck/nim-results/issues/30).
This commit is contained in:
parent
5c519d8582
commit
2cf408b960
|
@ -28,7 +28,7 @@ respective folders
|
|||
- `leb128` - utilities for working with LEB128-based formats (such as the varint style found in protobuf)
|
||||
- `objects` - get an object's base type at runtime, as a string
|
||||
- `ptrops` - pointer arithmetic utilities
|
||||
- `result` - friendly, exception-free value-or-error returns, similar to `Option[T]`, from [nim-result](https://github.com/arnetheduck/nim-result/)
|
||||
- `result` - moved to [nim-results](https://github.com/arnetheduck/nim-results/)
|
||||
- `shims` - backports of nim `devel` code to the stable version that Status is using
|
||||
- `sequtils2` - extensions to the `sequtils` module for working conveniently with `seq`
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ license = "MIT or Apache License 2.0"
|
|||
skipDirs = @["tests"]
|
||||
|
||||
requires "nim >= 1.2.0",
|
||||
"results",
|
||||
"unittest2"
|
||||
|
||||
let nimc = getEnv("NIMC", "nim") # Which nim compiler to use
|
||||
|
|
1256
stew/results.nim
1256
stew/results.nim
File diff suppressed because it is too large
Load Diff
|
@ -32,6 +32,4 @@ import
|
|||
test_sequtils2,
|
||||
test_sets,
|
||||
test_templateutils,
|
||||
test_results,
|
||||
test_results2,
|
||||
test_winacl
|
||||
|
|
|
@ -1,532 +0,0 @@
|
|||
{.used.}
|
||||
|
||||
# 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
|
||||
|
||||
block:
|
||||
func works(): R = R.ok(42)
|
||||
func works2(): R = result.ok(42)
|
||||
func works3(): R = ok(42)
|
||||
|
||||
func fails(): R = R.err("dummy")
|
||||
func fails2(): R = result.err("dummy")
|
||||
func fails3(): R = err("dummy")
|
||||
|
||||
let
|
||||
rOk = works()
|
||||
rOk2 = works2()
|
||||
rOk3 = works3()
|
||||
|
||||
rErr = fails()
|
||||
rErr2 = fails2()
|
||||
rErr3 = fails3()
|
||||
|
||||
doAssert rOk.isOk
|
||||
doAssert rOk2.isOk
|
||||
doAssert rOk3.isOk
|
||||
doAssert (not rOk.isErr)
|
||||
|
||||
doAssert rErr.isErr
|
||||
doAssert rErr2.isErr
|
||||
doAssert rErr3.isErr
|
||||
|
||||
# Mutate
|
||||
var x = rOk
|
||||
x.err("failed now")
|
||||
doAssert x.isErr
|
||||
doAssert x.error == "failed now"
|
||||
|
||||
# Combine
|
||||
doAssert (rOk and rErr).isErr
|
||||
doAssert (rErr and rOk).isErr
|
||||
doAssert (rOk or rErr).isOk
|
||||
doAssert (rErr or rOk).isOk
|
||||
|
||||
# Fail fast
|
||||
proc failFast(): int = raiseAssert "shouldn't evaluate"
|
||||
proc failFastR(): R = raiseAssert "shouldn't evaluate"
|
||||
|
||||
doAssert (rErr and failFastR()).isErr
|
||||
doAssert (rOk or failFastR()).isOk
|
||||
|
||||
# `and` heterogenous types
|
||||
doAssert (rOk and Result[string, string].ok($rOk.get())).get() == $(rOk[])
|
||||
|
||||
# `or` heterogenous types
|
||||
doAssert (rErr or Result[int, int].err(len(rErr.error))).error == len(rErr.error)
|
||||
|
||||
# Exception on access
|
||||
doAssert (try: (discard rOk.tryError(); false) except ResultError[int]: true)
|
||||
doAssert (try: (discard rErr.tryGet(); false) except ResultError[string]: true)
|
||||
|
||||
# Value access or default
|
||||
doAssert rOk.get(100) == rOk.get()
|
||||
doAssert rErr.get(100) == 100
|
||||
|
||||
doAssert rOk.get() == rOk.unsafeGet()
|
||||
|
||||
rOk.isOkOr: raiseAssert "should not end up in here"
|
||||
rErr.isErrOr: raiseAssert "should not end up in here"
|
||||
|
||||
rErr.isOkOr:
|
||||
doAssert error == rErr.error()
|
||||
|
||||
rOk.isErrOr:
|
||||
doAssert value == rOk.value()
|
||||
|
||||
doAssert rOk.valueOr(failFast()) == rOk.value()
|
||||
let rErrV = rErr.valueOr:
|
||||
error.len
|
||||
doAssert rErrV == rErr.error.len()
|
||||
|
||||
let rOkV = rOk.errorOr:
|
||||
$value
|
||||
doAssert rOkV == $rOk.get()
|
||||
|
||||
# Exceptions -> results
|
||||
block:
|
||||
func raises(): int =
|
||||
raise (ref CatchableError)(msg: "hello")
|
||||
func raisesVoid() =
|
||||
raise (ref CatchableError)(msg: "hello")
|
||||
|
||||
let c = catch:
|
||||
raises()
|
||||
doAssert c.isErr
|
||||
|
||||
when (NimMajor, NimMinor) >= (1, 6):
|
||||
# Earlier versions complain about the type of the raisesVoid expression
|
||||
let d = catch:
|
||||
raisesVoid()
|
||||
doAssert d.isErr
|
||||
|
||||
# De-reference
|
||||
when (NimMajor, NimMinor) >= (1, 6):
|
||||
{.warning[BareExcept]:off.}
|
||||
|
||||
try:
|
||||
echo rErr[]
|
||||
doAssert false
|
||||
except:
|
||||
discard
|
||||
|
||||
when (NimMajor, NimMinor) >= (1, 6):
|
||||
{.warning[BareExcept]:on.}
|
||||
|
||||
# Comparisons
|
||||
doAssert (rOk == rOk)
|
||||
doAssert (rErr == rErr)
|
||||
doAssert (rOk != rErr)
|
||||
|
||||
# Mapping
|
||||
doAssert (rOk.map(func(x: int): string = $x)[] == $rOk.value)
|
||||
doAssert (rOk.map(func(x: int) = discard)).isOk()
|
||||
|
||||
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!"))
|
||||
|
||||
# Casts and conversions
|
||||
doAssert rOk.mapConvert(int64)[] == int64(42)
|
||||
doAssert rOk.mapConvert(uint64)[] == uint64(42)
|
||||
doAssert rOk.mapCast(int8)[] == int8(42)
|
||||
|
||||
doAssert (rErr.orErr(32)).error == 32
|
||||
doAssert (rOk.orErr(failFast())).get() == rOk.get()
|
||||
|
||||
doAssert rErr.mapConvertErr(cstring).error() == cstring(rErr.error())
|
||||
doAssert rErr.mapCastErr(seq[byte]).error() == cast[seq[byte]](rErr.error())
|
||||
|
||||
# string conversion
|
||||
doAssert $rOk == "ok(42)"
|
||||
doAssert $rErr == "err(dummy)"
|
||||
|
||||
# Exception interop
|
||||
let e = capture(int, (ref ValueError)(msg: "test"))
|
||||
doAssert e.isErr
|
||||
doAssert e.error.msg == "test"
|
||||
|
||||
try:
|
||||
discard rOk.tryError()
|
||||
doAssert false, "should have raised"
|
||||
except ValueError:
|
||||
discard
|
||||
|
||||
try:
|
||||
discard e.tryGet()
|
||||
doAssert false, "should have raised"
|
||||
except ValueError as e:
|
||||
doAssert e.msg == "test"
|
||||
|
||||
# Nice way to checks
|
||||
if (let v = works(); v.isOk):
|
||||
doAssert v[] == v.value
|
||||
|
||||
# Expectations
|
||||
doAssert rOk.expect("testOk never fails") == 42
|
||||
|
||||
# Conversions to Opt
|
||||
doAssert rOk.optValue() == Opt.some(rOk.get())
|
||||
doAssert rOk.optError().isNone()
|
||||
doAssert rErr.optValue().isNone()
|
||||
doAssert rErr.optError() == Opt.some(rErr.error())
|
||||
|
||||
# Question mark operator
|
||||
func testQn(): Result[int, string] =
|
||||
let x = ?works() - ?works()
|
||||
ok(x)
|
||||
|
||||
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")
|
||||
|
||||
func testQn3(): Result[bool, string] =
|
||||
# different T but same E
|
||||
let x = ?works() - ?works()
|
||||
ok(x == 0)
|
||||
|
||||
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
|
||||
|
||||
# Flatten
|
||||
doAssert Result[R, string].ok(rOk).flatten() == rOk
|
||||
doAssert Result[R, string].ok(rErr).flatten() == rErr
|
||||
|
||||
# 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
|
||||
|
||||
# Collections
|
||||
block:
|
||||
var i = 0
|
||||
for v in rOk.values:
|
||||
doAssert v == rOk.value()
|
||||
i += 1
|
||||
doAssert i == 1
|
||||
|
||||
for v in rOk.errors:
|
||||
raiseAssert "not an error"
|
||||
|
||||
doAssert rOk.containsValue(rOk.value())
|
||||
doAssert not rOk.containsValue(rOk.value() + 1)
|
||||
|
||||
doAssert not rOk.containsError("test")
|
||||
|
||||
block:
|
||||
var i = 0
|
||||
for v in rErr.values:
|
||||
raiseAssert "not a value"
|
||||
|
||||
for v in rErr.errors:
|
||||
doAssert v == rErr.error()
|
||||
i += 1
|
||||
doAssert i == 1
|
||||
|
||||
doAssert rErr.containsError(rErr.error())
|
||||
doAssert not rErr.containsError(rErr.error() & "X")
|
||||
|
||||
doAssert not rErr.containsValue(42)
|
||||
|
||||
# Exception conversions - toException must not be inside a block
|
||||
type
|
||||
AnEnum = enum
|
||||
anEnumA
|
||||
anEnumB
|
||||
AnException = ref object of CatchableError
|
||||
v: AnEnum
|
||||
|
||||
func toException(v: AnEnum): AnException = AnException(v: v)
|
||||
|
||||
func testToException(): int =
|
||||
try:
|
||||
var r = Result[int, AnEnum].err(anEnumA)
|
||||
r.tryGet
|
||||
except AnException:
|
||||
42
|
||||
|
||||
doAssert testToException() == 42
|
||||
|
||||
type
|
||||
AnEnum2 = enum
|
||||
anEnum2A
|
||||
anEnum2B
|
||||
|
||||
func testToString(): int =
|
||||
try:
|
||||
var r = Result[int, AnEnum2].err(anEnum2A)
|
||||
r.tryGet
|
||||
except ResultError[AnEnum2]:
|
||||
42
|
||||
|
||||
doAssert testToString() == 42
|
||||
|
||||
block: # Result[void, E]
|
||||
type VoidRes = Result[void, int]
|
||||
|
||||
func worksVoid(): VoidRes = VoidRes.ok()
|
||||
func worksVoid2(): VoidRes = result.ok()
|
||||
func worksVoid3(): VoidRes = ok()
|
||||
|
||||
func failsVoid(): VoidRes = VoidRes.err(42)
|
||||
func failsVoid2(): VoidRes = result.err(42)
|
||||
func failsVoid3(): VoidRes = err(42)
|
||||
|
||||
let
|
||||
vOk = worksVoid()
|
||||
vOk2 = worksVoid2()
|
||||
vOk3 = worksVoid3()
|
||||
|
||||
vErr = failsVoid()
|
||||
vErr2 = failsVoid2()
|
||||
vErr3 = failsVoid3()
|
||||
|
||||
doAssert vOk.isOk
|
||||
doAssert vOk2.isOk
|
||||
doAssert vOk3.isOk
|
||||
doAssert (not vOk.isErr)
|
||||
|
||||
doAssert vErr.isErr
|
||||
doAssert vErr2.isErr
|
||||
doAssert vErr3.isErr
|
||||
|
||||
vOk.get()
|
||||
vOk.unsafeGet()
|
||||
vOk.expect("should never fail")
|
||||
vOk[]
|
||||
|
||||
# Comparisons
|
||||
doAssert (vOk == vOk)
|
||||
doAssert (vErr == vErr)
|
||||
doAssert (vOk != vErr)
|
||||
|
||||
# Mapping
|
||||
doAssert vOk.map(proc (): int = 42).get() == 42
|
||||
vOk.map(proc () = discard).get()
|
||||
|
||||
vOk.mapErr(proc(x: int): int = 10).get()
|
||||
vOk.mapErr(proc(x: int) = discard).get()
|
||||
|
||||
doAssert vErr.mapErr(proc(x: int): int = 10).error() == 10
|
||||
|
||||
# string conversion
|
||||
doAssert $vOk == "ok()"
|
||||
doAssert $vErr == "err(42)"
|
||||
|
||||
# Question mark operator
|
||||
func voidF(): VoidRes =
|
||||
ok()
|
||||
|
||||
func voidF2(): Result[int, int] =
|
||||
? voidF()
|
||||
|
||||
ok(42)
|
||||
|
||||
doAssert voidF2().isOk
|
||||
|
||||
# flatten
|
||||
doAssert Result[VoidRes, int].ok(vOk).flatten() == vOk
|
||||
doAssert Result[VoidRes, int].ok(vErr).flatten() == vErr
|
||||
|
||||
# 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
|
||||
|
||||
block: # Result[T, void] aka `Opt`
|
||||
type OptInt = Result[int, void]
|
||||
|
||||
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 == "none()"
|
||||
|
||||
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
|
||||
|
||||
doAssert Opt.some(42).get() == 42
|
||||
doAssert Opt.none(int).isNone()
|
||||
|
||||
# Construct Result from Opt
|
||||
doAssert oOk.orErr("error").value() == oOk.get()
|
||||
doAssert oErr.orErr("error").error() == "error"
|
||||
|
||||
# Collections
|
||||
block:
|
||||
var i = 0
|
||||
for v in oOk:
|
||||
doAssert v == oOk.value()
|
||||
i += 1
|
||||
doAssert i == 1
|
||||
|
||||
doAssert oOk.value() in oOk
|
||||
doAssert oOk.value() + 1 notin oOk
|
||||
|
||||
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"
|
||||
|
||||
block: # Constants
|
||||
# TODO https://github.com/nim-lang/Nim/issues/20699
|
||||
type
|
||||
WithOpt = object
|
||||
opt: Opt[int]
|
||||
const
|
||||
noneWithOpt =
|
||||
WithOpt(opt: Opt.none(int))
|
||||
proc checkIt(v: WithOpt) =
|
||||
doAssert v.opt.isNone()
|
||||
checkIt(noneWithOpt)
|
||||
|
||||
block: # TODO https://github.com/nim-lang/Nim/issues/22049
|
||||
var v: Result[(seq[int], seq[int]), int]
|
||||
v.ok((@[1], @[2]))
|
||||
let (a, b) = v.get()
|
||||
doAssert a == [1] and b == [2]
|
||||
let (c, d) = v.tryGet()
|
||||
doAssert c == [1] and d == [2]
|
||||
let (e, f) = v.unsafeGet()
|
||||
doAssert e == [1] and f == [2]
|
||||
|
||||
block:
|
||||
# withAssertOk evaluated as statement instead of expr
|
||||
# https://github.com/nim-lang/Nim/issues/22216
|
||||
func bug(): Result[uint16, string] =
|
||||
ok(1234)
|
||||
|
||||
const
|
||||
x = bug()
|
||||
y = x.value()
|
||||
|
||||
doAssert y == 1234
|
||||
|
||||
when (NimMajor, NimMinor) >= (1,6):
|
||||
# pre 1.6 nim vm have worse bug
|
||||
static:
|
||||
var z = bug()
|
||||
z.value() = 15
|
||||
let w = z.get()
|
||||
doAssert w == 15
|
||||
|
||||
let
|
||||
xx = bug()
|
||||
yy = x.value()
|
||||
|
||||
doAssert yy == 1234
|
|
@ -1,13 +0,0 @@
|
|||
import ../stew/results
|
||||
|
||||
{.used.}
|
||||
|
||||
# Oddly, this piece of code works when placed in `test_results.nim`
|
||||
# See also https://github.com/status-im/nim-stew/pull/167
|
||||
|
||||
template repeater(b: Opt[int]): untyped =
|
||||
# Check that Result can be used inside a template - this fails
|
||||
# sometimes with field access errors as noted in above issue
|
||||
b
|
||||
let x = repeater(Opt.none(int))
|
||||
doAssert x.isNone()
|
Loading…
Reference in New Issue