typesafe-monads/tests/test_result.py

167 lines
4.4 KiB
Python

from __future__ import annotations
from typing import Callable, List
from monads.maybe import Maybe, Just, Nothing
from monads.result import Result, Ok, Err, safe
def test_types() -> None:
m: Result[int, str] = Result.pure(1)
map: Result[int, str] = m.map(lambda x: x)
map_operator: Result[int, str] = m * (lambda x: x)
bind: Result[int, str] = m.bind(lambda x: Result.pure(x))
bind_operator: Result[int, str] = m >> (lambda x: Result.pure(x))
apply: Result[int, str] = m.apply(Result.pure(lambda x: x))
apply_operator: Result[int, str] = Result.pure(lambda x: x) & m
sequence: Result[List[int], str] = Result.sequence([m])
def test_bind_ok() -> None:
m: Result[int, str] = Ok(5)
increment: Callable[[int], Result[int, str]] = lambda x: Ok(x + 1)
assert Ok(6) == m.bind(increment)
def test_bind_err() -> None:
m: Result[int, str] = Err("oops")
increment: Callable[[int], Result[int, str]] = lambda x: Ok(x + 1)
assert Err("oops") == m.bind(increment)
def test_apply_ok_to_ok() -> None:
m: Result[int, str] = Ok(5)
increment: Callable[[int], int] = lambda x: x + 1
assert Ok(6) == m.apply(Result.pure(increment))
def test_apply_ok_to_err() -> None:
m: Result[int, str] = Err("oops")
increment: Callable[[int], int] = lambda x: x + 1
assert Err("oops") == m.apply(Result.pure(increment))
def test_apply_err_to_ok() -> None:
m: Result[int, str] = Ok(5)
f: Result[Callable[[int], int], str] = Err("oops")
assert Err("oops") == m.apply(f)
def test_apply_err_to_err() -> None:
m: Result[int, str] = Err("oops")
f: Result[Callable[[int], int], str] = Err("oops")
assert Err("oops") == m.apply(f)
def test_ok_withdefault() -> None:
m: Result[int, str] = Ok(5)
assert 5 == m.withDefault(0)
def test_err_withdefault() -> None:
m: Result[int, str] = Err("oops")
assert 0 == m.withDefault(0)
def test_map() -> None:
result: Result[int, str] = Ok(5)
mapped: Result[str, str] = result.map(str)
assert "5" == mapped.withDefault("0")
def test_map_infix() -> None:
result: Result[int, str] = Ok(5)
mapped: Result[str, str] = result * str
assert "5" == mapped.withDefault("0")
def test_map_error_err() -> None:
m: Result[int, str] = Err("oops")
assert Err(4) == m.mapError(len)
def test_map_error_ok() -> None:
m: Result[int, str] = Ok(123)
assert Ok(123) == m.mapError(len)
def test_bind() -> None:
result: Result[int, str] = Ok(5)
incremented: Result[int, str] = result.bind(lambda x: Ok(x + 1))
assert 6 == incremented.withDefault(0)
def test_bind_infix() -> None:
result: Result[int, str] = Ok(5)
incremented: Result[int, str] = result >> (lambda x: Ok(x + 1))
assert 6 == incremented.withDefault(0)
def test_pipeline() -> None:
class Frobnicator(object):
@classmethod
def create(cls, config: dict) -> Result[Frobnicator, str]:
return Ok(cls())
def dostuff(self) -> Result[list, str]:
return Ok(["a", "b", "c", "d"])
def load_config() -> Result[dict, str]:
return Ok({"foo": "bar"})
result: Result[int, str] = (
load_config()
>> Frobnicator.create
>> Frobnicator.dostuff
>> (lambda res: Ok(len(res)))
)
assert 4 == result.withDefault(0)
def test_unsafe_wrapped_function_returns_error() -> None:
error: Exception = Exception("oops")
@safe
def unsafe(x: int) -> int:
if x > 5:
raise error
else:
return x + 1
result: Result[int, Exception] = unsafe(10)
assert Err(error) == result
def test_safe_wrapped_function_returns_ok() -> None:
error: Exception = Exception("oops")
@safe
def unsafe(x: int) -> int:
if x > 5:
raise error
else:
return x + 1
result: Result[int, Exception] = unsafe(5)
assert Ok(6) == result
def test_from_just() -> None:
m: Maybe[int] = Just(6)
result: Result[int, str] = Result.fromMaybe(m, "error")
assert Ok(6) == result
def test_from_nothing() -> None:
m: Maybe[int] = Nothing()
result: Result[int, str] = Result.fromMaybe(m, "error")
assert Err("error") == result
def test_ok_to_maybe() -> None:
result: Result[int, str] = Ok(6)
assert Just(6) == result.toMaybe()
def test_err_to_maybe() -> None:
result: Result[int, str] = Err("error")
assert Nothing() == result.toMaybe()