Add Either type
This commit is contained in:
81
src/op.nim
81
src/op.nim
@@ -25,28 +25,61 @@
|
||||
## assert r.isOk == false
|
||||
## assert r.error == "Cannot divide by zero!"
|
||||
|
||||
import macros
|
||||
|
||||
type
|
||||
OP*[T] = object
|
||||
## Object to wrap the result of an operation.
|
||||
##
|
||||
## The type is discriminated by the `isOK` bool.
|
||||
## So it is an compiler error to try to access the value without checking
|
||||
## if the operation was successful.
|
||||
##
|
||||
## - `isOk`: Indicates if the operation was successful
|
||||
## - `val`: If successful, this will hold the real result value
|
||||
## - `error`: Otherwise this will hold an error message
|
||||
case isOk*: bool
|
||||
Either*[A, B] = object
|
||||
case isA: bool
|
||||
of true:
|
||||
val*: T
|
||||
a: A
|
||||
of false:
|
||||
error*: string
|
||||
b: B
|
||||
|
||||
proc ok*[T](val: T): OP[T] {.inline.} =
|
||||
OP*[T] = Either[T, string]
|
||||
## Alias of `Either` with a string as error message
|
||||
|
||||
|
||||
template checkA*(self: Either): bool = self.isA
|
||||
template getA*[A, B](self: Either[A, B]): A = self.a
|
||||
template getB*[A, B](self: Either[A, B]): B = self.b
|
||||
|
||||
proc newEitherA*[A, B](a: A): Either[A, B] {.inline.} =
|
||||
Either[A, B](isA: true, a: a)
|
||||
|
||||
proc newEitherB*[A, B](b: B): Either[A, B] {.inline.} =
|
||||
Either[A, B](isA: false, b: b)
|
||||
|
||||
template isOK*(self: OP): bool = checkA(self)
|
||||
template val*(self: OP): auto = getA(self)
|
||||
template error*(self: OP): auto = getB(self)
|
||||
|
||||
template ok*[T](val: T): OP[T] =
|
||||
## Wraps the given value in a successful operation result.
|
||||
OP[T](isOK: true, val: val)
|
||||
newEitherA[T, string](val)
|
||||
|
||||
proc fail*(op: OP, msg: string): OP {.inline.} =
|
||||
template ok*[T](self: var OP[T], val: T) =
|
||||
## Set the result to the given value
|
||||
self = ok val
|
||||
|
||||
template fail*[T](O: type OP[T], msg: string): O =
|
||||
## Will create a new operation result with the given error message.
|
||||
## The type for the operation result is given explicitly.
|
||||
##
|
||||
## **See Also:**
|
||||
## - `fail proc<#fail,OP,string>`_
|
||||
## - `fail template<#fail.t,static[string]>`_
|
||||
newEitherB[T, string](msg)
|
||||
|
||||
template fail*(T: typedesc, msg: string): OP[T] =
|
||||
## Will create a new operation result with the given error message.
|
||||
## The type for the operation result is given explicitly.
|
||||
##
|
||||
## **See Also:**
|
||||
## - `fail proc<#fail,OP,string>`_
|
||||
## - `fail template<#fail.t,static[string]>`_
|
||||
newEitherB[T, string](msg)
|
||||
|
||||
template fail*(op: OP, msg: string): OP =
|
||||
## Will create a new operation result with the given error message.
|
||||
## The type for the operation result is taken from the `op` argument.
|
||||
runnableExamples:
|
||||
@@ -56,19 +89,7 @@ proc fail*(op: OP, msg: string): OP {.inline.} =
|
||||
let data = someProc()
|
||||
assert data.isOk == false
|
||||
assert data.error == "Not implemented!"
|
||||
OP(isOK: false, error: msg)
|
||||
|
||||
proc fail*(T: typedesc, msg: string): OP[T] {.inline.} =
|
||||
## Will create a new operation result with the given error message.
|
||||
## The type for the operation result is given explicitly.
|
||||
##
|
||||
## **See Also:**
|
||||
## - `fail proc<#fail,OP,string>`_
|
||||
## - `fail template<#fail.t,static[string]>`_
|
||||
OP[T](isOK: false, error: msg)
|
||||
|
||||
template fail*[T](O: type OP[T], msg: string): O = O(isOK: false, error: msg)
|
||||
|
||||
template fail*(msg: static[string]): auto = fail(typeof(result), msg)
|
||||
fail(typeof(op), msg)
|
||||
|
||||
|
||||
template fail*(msg: static[string]): auto = fail(typeof(result), msg)
|
||||
@@ -4,6 +4,12 @@ import unittest
|
||||
import op
|
||||
|
||||
suite "Basic tests":
|
||||
proc divide(a, b: int): OP[float] =
|
||||
if b == 0:
|
||||
return fail "Cannot divide by zero!"
|
||||
else:
|
||||
return ok a / b
|
||||
|
||||
test "Check OK":
|
||||
let test = ok 1
|
||||
check test.isOk == true
|
||||
@@ -16,9 +22,9 @@ suite "Basic tests":
|
||||
proc createValue: OP[string] =
|
||||
let myString = "This is test code!"
|
||||
ok myString
|
||||
let data = createValue()
|
||||
check data.isOk
|
||||
check data.val == "This is test code!"
|
||||
let r = createValue()
|
||||
check r.isOk
|
||||
check r.val == "This is test code!"
|
||||
|
||||
test "Check failing result proc":
|
||||
proc someProc(): OP[int] =
|
||||
@@ -54,38 +60,53 @@ suite "Basic tests":
|
||||
check data.isOk == false
|
||||
check data.error == "data got corrupted"
|
||||
|
||||
test "Check divider proc":
|
||||
proc divide(a, b: int): OP[float] =
|
||||
if b == 0:
|
||||
return fail(float, "Cannot divide by zero!")
|
||||
else:
|
||||
return ok a / b
|
||||
|
||||
let
|
||||
a = 42
|
||||
b = 0
|
||||
let r = divide(a, b)
|
||||
check r.isOk == false
|
||||
check r.error == "Cannot divide by zero!"
|
||||
test "Check divider OK":
|
||||
let r = divide(12, 6)
|
||||
check r.isOk == true
|
||||
check r.val == 2
|
||||
|
||||
test "Check implicit fail type":
|
||||
proc divide(a, b: int): OP[float] =
|
||||
if b == 0:
|
||||
return fail "Cannot divide by zero!"
|
||||
else:
|
||||
return ok a / b
|
||||
|
||||
let r = divide(1, 0)
|
||||
check r.isOk == false
|
||||
check r.error == "Cannot divide by zero!"
|
||||
|
||||
test "Check type of impolicit fail":
|
||||
proc divide(a, b: int): OP[float] =
|
||||
if b == 0:
|
||||
return fail "Cannot divide by zero!"
|
||||
else:
|
||||
return ok a / b
|
||||
test "Check case":
|
||||
let r = divide(1, 2)
|
||||
case r.isOK:
|
||||
of true:
|
||||
check r.val == 0.5
|
||||
of false:
|
||||
echo r.error
|
||||
|
||||
let r = divide(1, 0)
|
||||
check r.isOk == false
|
||||
check r.error == "Cannot divide by zero!"
|
||||
test "Check hiding of Either":
|
||||
let r = divide(1, 2)
|
||||
case r.isOK:
|
||||
of true:
|
||||
check r.val == 0.5
|
||||
of false:
|
||||
echo r.error
|
||||
|
||||
test "Expect invalid field":
|
||||
let r = divide(1, 2)
|
||||
expect FieldError:
|
||||
echo r.error
|
||||
|
||||
test "Expect in case":
|
||||
let r = divide(1, 2)
|
||||
case r.isOK:
|
||||
of true:
|
||||
expect FieldError:
|
||||
echo r.error
|
||||
of false:
|
||||
echo r.error
|
||||
|
||||
# These will not work. How could this be done?
|
||||
# test "Expect access before check":
|
||||
# let r = divide(1, 2)
|
||||
# expect FieldError:
|
||||
# echo r.val
|
||||
|
||||
# test "Expect invalid field after check":
|
||||
# let r = divide(1, 2)
|
||||
# if r.isOK:
|
||||
# echo r.error
|
||||
Reference in New Issue
Block a user