diff --git a/src/op.nim b/src/op.nim index 33c3135..5faeb17 100644 --- a/src/op.nim +++ b/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) \ No newline at end of file diff --git a/tests/tests.nim b/tests/tests.nim index 0b031e2..2dc2e36 100644 --- a/tests/tests.nim +++ b/tests/tests.nim @@ -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 \ No newline at end of file