use std::error::Error; pub trait FatalError: Error {} pub enum Flow<A, FE, E> { Ok(A), Fatal(FE), Err(E), } impl<A, FE, E> Flow<A, FE, E> { pub fn map<B, O>(self, mapper: O) -> Flow<B, FE, E> where O: FnOnce(A) -> B, { match self { Flow::Ok(val) => Flow::Ok(mapper(val)), Flow::Fatal(err) => Flow::Fatal(err), Flow::Err(err) => Flow::Err(err), } } pub fn and_then<B, O>(self, handler: O) -> Flow<B, FE, E> where O: FnOnce(A) -> Flow<B, FE, E>, { match self { Flow::Ok(val) => handler(val), Flow::Fatal(err) => Flow::Fatal(err), Flow::Err(err) => Flow::Err(err), } } pub fn map_err<F, O>(self, mapper: O) -> Flow<A, FE, F> where O: FnOnce(E) -> F, { match self { Flow::Ok(val) => Flow::Ok(val), Flow::Fatal(err) => Flow::Fatal(err), Flow::Err(err) => Flow::Err(mapper(err)), } } pub fn or_else<O, F>(self, handler: O) -> Flow<A, FE, F> where O: FnOnce(E) -> Flow<A, FE, F>, { match self { Flow::Ok(val) => Flow::Ok(val), Flow::Fatal(err) => Flow::Fatal(err), Flow::Err(err) => handler(err), } } } impl<A, FE, E> From<Result<A, E>> for Flow<A, FE, E> { fn from(r: Result<A, E>) -> Self { match r { Ok(val) => Flow::Ok(val), Err(err) => Flow::Err(err), } } } pub fn ok<A, FE: FatalError, E: Error>(val: A) -> Flow<A, FE, E> { Flow::Ok(val) } pub fn error<A, FE: FatalError, E: Error>(err: E) -> Flow<A, FE, E> { Flow::Err(err) } pub fn fatal<A, FE: FatalError, E: Error>(err: FE) -> Flow<A, FE, E> { Flow::Fatal(err) } #[macro_export] macro_rules! return_fatal { ($x:expr) => { match $x { Flow::Fatal(err) => return Flow::Fatal(err), Flow::Err(err) => Err(err), Flow::Ok(val) => Ok(val), } }; } #[macro_export] macro_rules! return_error { ($x:expr) => { match $x { Flow::Ok(val) => val, Flow::Err(err) => return Flow::Err(err), Flow::Fatal(err) => return Flow::Fatal(err), } }; } #[cfg(test)] mod test { use super::*; use thiserror::Error; #[derive(Debug, Error)] enum FatalError { #[error("A fatal error occurred")] FatalError, } impl super::FatalError for FatalError {} impl PartialEq for FatalError { fn eq(&self, rhs: &Self) -> bool { true } } #[derive(Debug, Error)] enum Error { #[error("an error occurred")] Error, } impl PartialEq for Error { fn eq(&self, rhs: &Self) -> bool { true } } impl PartialEq for Flow<i32, FatalError, Error> { fn eq(&self, rhs: &Self) -> bool { match (self, rhs) { (Flow::Ok(val), Flow::Ok(rhs)) => val == rhs, (Flow::Err(_), Flow::Err(_)) => true, (Flow::Fatal(_), Flow::Fatal(_)) => true, _ => false, } } } impl std::fmt::Debug for Flow<i32, FatalError, Error> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Flow::Ok(val) => f.write_fmt(format_args!("Flow::Ok {}", val)), Flow::Err(err) => f.write_fmt(format_args!("Flow::Err {:?}", err)), Flow::Fatal(err) => f.write_fmt(format_args!("Flow::Fatal {:?}", err)), } } } #[test] fn it_can_map_things() { let success = ok(15); assert_eq!(ok(16), success.map(|v| v + 1)); } #[test] fn it_can_chain_success() { let success = ok(15); assert_eq!(ok(16), success.and_then(|v| ok(v + 1))); } #[test] fn it_can_handle_an_error() { let failure = error(Error::Error); assert_eq!(ok(16), failure.or_else(|_| ok(16))); } #[test] fn early_exit_on_fatal() { fn ok_func() -> Flow<i32, FatalError, Error> { let value = return_fatal!(ok::<i32, FatalError, Error>(15)); match value { Ok(_) => ok(14), Err(err) => error(err), } } fn err_func() -> Flow<i32, FatalError, Error> { let value = return_fatal!(error::<i32, FatalError, Error>(Error::Error)); match value { Ok(_) => panic!("shouldn't have gotten here"), Err(err) => ok(0), } } fn fatal_func() -> Flow<i32, FatalError, Error> { return_fatal!(fatal::<i32, FatalError, Error>(FatalError::FatalError)); panic!("failed to bail"); } fatal_func(); assert_eq!(ok_func(), ok(14)); assert_eq!(err_func(), ok(0)); } #[test] fn it_can_early_exit_on_all_errors() { fn ok_func() -> Flow<i32, FatalError, Error> { let value = return_error!(ok::<i32, FatalError, Error>(15)); assert_eq!(value, 15); ok(14) } fn err_func() -> Flow<i32, FatalError, Error> { return_error!(error::<i32, FatalError, Error>(Error::Error)); panic!("failed to bail"); } fn fatal_func() -> Flow<i32, FatalError, Error> { return_error!(fatal::<i32, FatalError, Error>(FatalError::FatalError)); panic!("failed to bail"); } fatal_func(); assert_eq!(ok_func(), ok(14)); err_func(); } }