diff --git a/flow/readme.md b/flow/readme.md new file mode 100644 index 0000000..0e3cafa --- /dev/null +++ b/flow/readme.md @@ -0,0 +1,3 @@ +# Flow + +This is a library to help with application control flow, separating fatal errors from normal recoverable errors. This is almost entirely inspired by [Error Handling in a Correctness-Critical Rust Project](https://sled.rs/errors.html) in the sled.rs library. diff --git a/flow/src/lib.rs b/flow/src/lib.rs index 10be154..6356e26 100644 --- a/flow/src/lib.rs +++ b/flow/src/lib.rs @@ -1,14 +1,40 @@ +//! Control Flow for Correctness-Critical applications +//! +//! https://sled.rs/errors.html +//! +//! Where the sled.rs library uses `Result, FatalError>`, these are a little hard to +//! work with. This library works out a set of utility functions that allow us to work with the +//! nested errors in the same way as a regular Result. use std::error::Error; +/// Implement this trait for the application's fatal errors. +/// +/// Fatal errors should be things that should trigger an application shutdown. Applications should +/// not try to recover from fatal errors, but simply bring the app to the safest shutdown point and +/// report the best possible information to the user. +/// +/// Examples: database corruption, or the database is unavailable in an application that cannot +/// function without it. Graphics environment cannot be initialized in a GUI app. +/// +/// Applications should generally have only one FatalError type. There are no handling utilities +/// for Fatal conditions, so Fatal conditions must be handled through an ordinary `match` +/// statement. pub trait FatalError: Error {} +/// Flow represents a return value that might be a success, might be a fatal error, or +/// might be a normal handleable error. pub enum Flow { + /// The operation was successful Ok(A), + /// The operation encountered a fatal error. These should be bubbled up to a level that can + /// safely shut the application down. Fatal(FE), + /// Ordinary errors. These should be handled and the application should recover gracefully. Err(E), } impl Flow { + /// Apply an infallible function to a successful value. pub fn map(self, mapper: O) -> Flow where O: FnOnce(A) -> B, @@ -20,6 +46,9 @@ impl Flow { } } + /// Apply a potentially fallible function to a successful value. + /// + /// Like `Result.and_then`, the mapping function can itself fail. pub fn and_then(self, handler: O) -> Flow where O: FnOnce(A) -> Flow, @@ -31,6 +60,9 @@ impl Flow { } } + /// Map a normal error from one type to another. This is useful for converting an error from + /// one type to another, especially in re-throwing an underlying error. `?` syntax does not + /// work with `Flow`, so you will likely need to use this a lot. pub fn map_err(self, mapper: O) -> Flow where O: FnOnce(E) -> F, @@ -42,6 +74,7 @@ impl Flow { } } + /// Provide a function to use to recover from (or simply re-throw) an error. pub fn or_else(self, handler: O) -> Flow where O: FnOnce(E) -> Flow, @@ -54,6 +87,8 @@ impl Flow { } } +/// Convert from a normal `Result` type to a `Flow` type. The error condition for a `Result` will +/// be treated as `Flow::Err`, never `Flow::Fatal`. impl From> for Flow { fn from(r: Result) -> Self { match r { @@ -63,19 +98,23 @@ impl From> for Flow { } } +/// Convenience function to create an ok value. pub fn ok(val: A) -> Flow { Flow::Ok(val) } +/// Convenience function to create an error value. pub fn error(err: E) -> Flow { Flow::Err(err) } +/// Convenience function to create a fatal value. pub fn fatal(err: FE) -> Flow { Flow::Fatal(err) } #[macro_export] +/// Return early from the current function if the value is a fatal error. macro_rules! return_fatal { ($x:expr) => { match $x { @@ -87,6 +126,7 @@ macro_rules! return_fatal { } #[macro_export] +/// Return early from the current function is the value is an error. macro_rules! return_error { ($x:expr) => { match $x {