Without statement for Results provides access to errors

This commit is contained in:
Mark Spanbroek 2022-01-10 17:48:59 +01:00 committed by markspanbroek
parent 91a38040ea
commit ef29000f94
4 changed files with 113 additions and 0 deletions

View File

@ -194,6 +194,20 @@ let value = fails() |? @[]
let sum = works()[3] + 40
```
### Without statement
The `without` statement can also be used with Results. It provides access to any
errors that may arise:
```nim
proc someProc(r: ?!int) =
without value =? r, error:
# use `error` to get the error from r
return
# use value
```
### Catching errors
When you want to use Results, but need to call a proc that may raise an

View File

@ -6,6 +6,7 @@ import ./chaining
import ./indexing
import ./operators
import ./without
import ./withoutresult
include ./errorban
@ -14,6 +15,7 @@ export binding
export chaining
export indexing
export without
export withoutresult
type ResultFailure* = object of CatchableError

View File

@ -0,0 +1,22 @@
import ./binding
import ./without
template without*(expression, errorname, body) =
## Used to place guards that ensure that a Result contains a value.
## Exposes error when Result does not contain a value.
var error: ref CatchableError
# override =? operator such that it stores the error if there is one
template `=?`(name, result): bool =
when result is Result:
if result.isFailure:
error = result.error
when result is Option:
if result.isNone:
error = newException(ValueError, "Option is set to `none`")
binding.`=?`(name, result)
without expression:
template errorname: ref CatchableError = error
body

View File

@ -180,6 +180,69 @@ suite "result":
test1()
test2()
test "without statement can expose error":
proc test =
without a =? int.failure "some error", error:
check error.msg == "some error"
return
fail
test()
test "without statement only exposes error variable inside block":
proc test =
without a =? 42.success, errorvar:
fail
discard errorvar # fixes warning about unused variable "errorvar"
return
check not compiles errorvar
test()
test "without statements with multiple bindings exposes first error":
proc test1 =
without (a =? int.failure "error 1") and
(b =? int.failure "error 2"),
error:
check error.msg == "error 1"
return
fail
proc test2 =
without (a =? 42.success) and (b =? int.failure "error 2"), error:
check error.msg == "error 2"
return
fail
test1()
test2()
test "without statement with error evaluates result only once":
proc test =
var count = 0
without a =? (inc count; int.failure "error"):
check count == 1
return
fail
test()
test "without statement with error handles options as well":
proc test1 =
without a =? int.none and b =? int.failure "error", error:
check error.msg == "Option is set to `none`"
return
fail
proc test2 =
without a =? 42.some and b =? int.failure "error", error:
check error.msg == "error"
return
fail
test1()
test2()
test "catch can be used to convert exceptions to results":
check parseInt("42").catch == 42.success
check parseInt("foo").catch.error of ValueError
@ -286,6 +349,18 @@ suite "result":
let converted = works().option
check (converted == @[1, 1, 2, 2, 2].some)
# Without statement
proc someProc(r: ?!int) =
without value =? r, error:
check error.msg == "some error"
return
check value == 42
someProc(42.success)
someProc(int.failure "some error")
import pkg/questionable/resultsbase
suite "result compatibility":