diff --git a/Readme.md b/Readme.md index 34e7a39..86f1716 100644 --- a/Readme.md +++ b/Readme.md @@ -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 diff --git a/questionable/results.nim b/questionable/results.nim index 7b4a166..e086ad6 100644 --- a/questionable/results.nim +++ b/questionable/results.nim @@ -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 diff --git a/questionable/withoutresult.nim b/questionable/withoutresult.nim new file mode 100644 index 0000000..087a3e5 --- /dev/null +++ b/questionable/withoutresult.nim @@ -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 diff --git a/testmodules/result/test.nim b/testmodules/result/test.nim index 367baa4..371c4a1 100644 --- a/testmodules/result/test.nim +++ b/testmodules/result/test.nim @@ -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":