Compare commits
6 Commits
27e1691854
...
fe6097ac5e
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | fe6097ac5e | |
Savanni D'Gerinel | 6d04e15a57 | |
Savanni D'Gerinel | d0f6a042e4 | |
Savanni D'Gerinel | 911f4ad81e | |
Savanni D'Gerinel | b756e8ca81 | |
Savanni D'Gerinel | 3cb742d863 |
|
@ -756,6 +756,14 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "error-training"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"result-extended",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "etcetera"
|
name = "etcetera"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -861,13 +869,6 @@ dependencies = [
|
||||||
"miniz_oxide 0.7.1",
|
"miniz_oxide 0.7.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "flow"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fluent"
|
name = "fluent"
|
||||||
version = "0.16.0"
|
version = "0.16.0"
|
||||||
|
@ -3119,6 +3120,13 @@ dependencies = [
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "result-extended"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsa"
|
name = "rsa"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
|
|
|
@ -7,8 +7,8 @@ members = [
|
||||||
"cyberpunk-splash",
|
"cyberpunk-splash",
|
||||||
"dashboard",
|
"dashboard",
|
||||||
"emseries",
|
"emseries",
|
||||||
|
"error-training",
|
||||||
"file-service",
|
"file-service",
|
||||||
"flow",
|
|
||||||
"fluent-ergonomics",
|
"fluent-ergonomics",
|
||||||
"geo-types",
|
"geo-types",
|
||||||
"gm-control-panel",
|
"gm-control-panel",
|
||||||
|
@ -17,7 +17,8 @@ members = [
|
||||||
"kifu/core",
|
"kifu/core",
|
||||||
"kifu/gtk",
|
"kifu/gtk",
|
||||||
"memorycache",
|
"memorycache",
|
||||||
|
"nom-training",
|
||||||
|
"result-extended",
|
||||||
"screenplay",
|
"screenplay",
|
||||||
"sgf",
|
"sgf",
|
||||||
"nom-training",
|
|
||||||
]
|
]
|
||||||
|
|
4
build.sh
4
build.sh
|
@ -10,8 +10,8 @@ RUST_ALL_TARGETS=(
|
||||||
"cyberpunk-splash"
|
"cyberpunk-splash"
|
||||||
"dashboard"
|
"dashboard"
|
||||||
"emseries"
|
"emseries"
|
||||||
|
"error-training"
|
||||||
"file-service"
|
"file-service"
|
||||||
"flow"
|
|
||||||
"fluent-ergonomics"
|
"fluent-ergonomics"
|
||||||
"geo-types"
|
"geo-types"
|
||||||
"gm-control-panel"
|
"gm-control-panel"
|
||||||
|
@ -20,6 +20,8 @@ RUST_ALL_TARGETS=(
|
||||||
"kifu-core"
|
"kifu-core"
|
||||||
"kifu-gtk"
|
"kifu-gtk"
|
||||||
"memorycache"
|
"memorycache"
|
||||||
|
"nom-training"
|
||||||
|
"result-extended"
|
||||||
"screenplay"
|
"screenplay"
|
||||||
"sgf"
|
"sgf"
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "error-training"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "error_training"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "error-training"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = { version = "1" }
|
||||||
|
result-extended = { path = "../result-extended" }
|
|
@ -0,0 +1,76 @@
|
||||||
|
//! Flow-style error handling is conceptually the same as sled-style, but with macros to help
|
||||||
|
//! out with the data structures. The result of a Flow-able operation is Flow<Value,
|
||||||
|
//! FatalError, Error>.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use ::result_extended::{error, fatal, ok, return_error, return_fatal, Result};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub struct DB(HashMap<String, i8>);
|
||||||
|
|
||||||
|
impl DB {
|
||||||
|
pub fn new(lst: Vec<(String, i8)>) -> Self {
|
||||||
|
Self(lst.into_iter().collect::<HashMap<String, i8>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a value from the database. Throw a fatal error with the "fail" key, but
|
||||||
|
/// otherwise return either the value or DatabaseError::NotFound.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use error_training::{*, flow::*};
|
||||||
|
/// use ::result_extended::Result;
|
||||||
|
///
|
||||||
|
/// let db = DB::new(vec![("a".to_owned(), 15), ("b".to_owned(), 0)]);
|
||||||
|
/// assert_eq!(db.get("fail"), Result::Fatal(FatalError::DatabaseCorruption));
|
||||||
|
/// assert_eq!(db.get("a"), Result::Ok(15));
|
||||||
|
/// assert_eq!(db.get("c"), Result::Err(DatabaseError::NotFound));
|
||||||
|
/// ```
|
||||||
|
pub fn get(&self, key: &str) -> Result<i8, DatabaseError, FatalError> {
|
||||||
|
if key == "fail" {
|
||||||
|
fatal(FatalError::DatabaseCorruption)
|
||||||
|
} else {
|
||||||
|
// Result::from(self.0.get(key).copied().ok_or(DatabaseError::NotFound))
|
||||||
|
self.0
|
||||||
|
.get(key)
|
||||||
|
.copied()
|
||||||
|
.ok_or(DatabaseError::NotFound)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op(val: i8) -> std::result::Result<i8, MathError> {
|
||||||
|
if val as i32 + 120_i32 > (i8::MAX as i32) {
|
||||||
|
Err(MathError::ExceedsMaxint)
|
||||||
|
} else {
|
||||||
|
Ok(val + 120)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function exists to test several of the major cases. This is where I will figure
|
||||||
|
/// out how to handle everything and also have clean code.
|
||||||
|
/// ```rust
|
||||||
|
/// use error_training::{*, flow::*};
|
||||||
|
/// use ::result_extended::Result;
|
||||||
|
///
|
||||||
|
/// let db = DB::new(vec![("a".to_owned(), 15), ("b".to_owned(), 0)]);
|
||||||
|
/// assert_eq!(run_op(&db, "a"), Result::Ok(i8::MAX));
|
||||||
|
/// assert_eq!(run_op(&db, "b"), Result::Ok(120));
|
||||||
|
/// assert_eq!(run_op(&db, "c"), Result::Ok(0));
|
||||||
|
/// assert_eq!(run_op(&db, "fail"), Result::Fatal(FatalError::DatabaseCorruption));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// I have defined this function such that a database miss becomes a 0 and no operation
|
||||||
|
/// will be performed on it. Since that is the only database error that can occur, this
|
||||||
|
/// function can only return a `MathError` or a `FatalError`.
|
||||||
|
pub fn run_op(db: &DB, key: &str) -> Result<i8, MathError, FatalError> {
|
||||||
|
let res = match return_fatal!(db.get(key)) {
|
||||||
|
Err(DatabaseError::NotFound) => Ok(0),
|
||||||
|
Ok(val) => op(val),
|
||||||
|
};
|
||||||
|
|
||||||
|
Result::from(res).or_else(|err| match err {
|
||||||
|
MathError::ExceedsMaxint => ok(i8::MAX),
|
||||||
|
_ => error(err),
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub mod flow;
|
||||||
|
pub mod sled;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Error, PartialEq)]
|
||||||
|
pub enum FatalError {
|
||||||
|
#[error("Database corruption detected")]
|
||||||
|
DatabaseCorruption,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::result_extended::FatalError for FatalError {}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Error, PartialEq)]
|
||||||
|
pub enum MathError {
|
||||||
|
#[error("divide by zero is not defined")]
|
||||||
|
DivideByZero,
|
||||||
|
|
||||||
|
#[error("result exceeds maxint")]
|
||||||
|
ExceedsMaxint,
|
||||||
|
|
||||||
|
#[error("result exceeds minint")]
|
||||||
|
ExceedsMinint,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Error, PartialEq)]
|
||||||
|
pub enum DatabaseError {
|
||||||
|
#[error("value not found")]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Error, PartialEq)]
|
||||||
|
pub enum OperationError {
|
||||||
|
#[error("database error occurred: {0}")]
|
||||||
|
DatabaseError(DatabaseError),
|
||||||
|
|
||||||
|
#[error("math error occurred: {0}")]
|
||||||
|
MathError(MathError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DatabaseError> for OperationError {
|
||||||
|
fn from(err: DatabaseError) -> Self {
|
||||||
|
Self::DatabaseError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MathError> for OperationError {
|
||||||
|
fn from(err: MathError) -> Self {
|
||||||
|
Self::MathError(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
//! Error handling practice.
|
||||||
|
//!
|
||||||
|
//! The purpose of this crate is to demonstrate error handling in a couple of different scenarios
|
||||||
|
//! so that I have clear templates to refer when doing development, instead of hand-waving or
|
||||||
|
//! putting error handling off into the unspecified future.
|
||||||
|
//!
|
||||||
|
//! I am going to demonstrate error handling in the style of [Error Handling in a
|
||||||
|
//! Correctness-Critical Rust Project | sled-rs.github.io](https://sled.rs/errors.html) and in my
|
||||||
|
//! reformulation of it using Flow.
|
||||||
|
//!
|
||||||
|
//! I will also test out additional libraries in the same scenarios:
|
||||||
|
//!
|
||||||
|
//! - anyhow
|
||||||
|
//!
|
||||||
|
//! A database exists with some numbers. Mathmatical calculations will be performed on those
|
||||||
|
//! numbers. Some calculations are invalid and should fail. In some cases, those should be reported
|
||||||
|
//! to the user, and in other cases those can be recovered. Sometimes a calculation needs to be
|
||||||
|
//! performed on a value that doesn't exist, which is also a failure. However, sometimes, the
|
||||||
|
//! database will detect corruption, wich is fatal and should terminate the "app".
|
||||||
|
//!
|
||||||
|
//! In these scenarios, the "app" is a top-level function which runs the scenario. This particular
|
||||||
|
//! app should never crash, just show where crashes would happen.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
//! Sled-style error handling is based on Result<Result<Value, LocalError>, FatalError>.
|
||||||
|
//! FatalErrors do not get resolved. LocalErrors get bubbled up until they can be handled.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub struct DB(HashMap<String, i8>);
|
||||||
|
|
||||||
|
impl DB {
|
||||||
|
pub fn new(lst: Vec<(String, i8)>) -> Self {
|
||||||
|
Self(lst.into_iter().collect::<HashMap<String, i8>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a value from the database. Throw a fatal error with the "fail" key, but
|
||||||
|
/// otherwise return either the value or DatabaseError::NotFound.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use error_training::{*, sled::*};
|
||||||
|
///
|
||||||
|
/// let db = DB::new(vec![("a".to_owned(), 15), ("b".to_owned(), 0)]);
|
||||||
|
/// assert_eq!(db.get("fail"), Err(FatalError::DatabaseCorruption));
|
||||||
|
/// assert_eq!(db.get("a"), Ok(Ok(15)));
|
||||||
|
/// assert_eq!(db.get("c"), Ok(Err(DatabaseError::NotFound)));
|
||||||
|
/// ```
|
||||||
|
pub fn get(&self, key: &str) -> Result<Result<i8, DatabaseError>, FatalError> {
|
||||||
|
if key == "fail" {
|
||||||
|
Err(FatalError::DatabaseCorruption)
|
||||||
|
} else {
|
||||||
|
Ok(self.0.get(key).copied().ok_or(DatabaseError::NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op(val: i8) -> Result<i8, MathError> {
|
||||||
|
if val as i32 + 120_i32 > (i8::MAX as i32) {
|
||||||
|
Err(MathError::ExceedsMaxint)
|
||||||
|
} else {
|
||||||
|
Ok(val + 120)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function exists to test several of the major cases. This is where I will figure out
|
||||||
|
/// how to handle everything and also have clean code.
|
||||||
|
/// ```rust
|
||||||
|
/// use error_training::{*, sled::*};
|
||||||
|
///
|
||||||
|
/// let db = DB::new(vec![("a".to_owned(), 15), ("b".to_owned(), 0)]);
|
||||||
|
/// assert_eq!(run_op(&db, "a"), Ok(Ok(i8::MAX)));
|
||||||
|
/// assert_eq!(run_op(&db, "b"), Ok(Ok(120)));
|
||||||
|
/// assert_eq!(run_op(&db, "c"), Ok(Ok(0)));
|
||||||
|
/// assert_eq!(run_op(&db, "fail"), Err(FatalError::DatabaseCorruption));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// I have defined this function such that a database miss becomes a 0 and no operation will be
|
||||||
|
/// performed on it. Since that is the only database error that can occur, this function can
|
||||||
|
/// only return a `MathError` or a `FatalError`.
|
||||||
|
pub fn run_op(db: &DB, key: &str) -> Result<Result<i8, MathError>, FatalError> {
|
||||||
|
let res = match db.get(key)? {
|
||||||
|
Err(DatabaseError::NotFound) => Ok(0),
|
||||||
|
Ok(val) => op(val),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(res.or_else(|err| match err {
|
||||||
|
MathError::ExceedsMaxint => Ok(127),
|
||||||
|
err => Err(err),
|
||||||
|
}))
|
||||||
|
}
|
|
@ -18,8 +18,6 @@ path = "src/main.rs"
|
||||||
name = "auth-cli"
|
name = "auth-cli"
|
||||||
path = "src/bin/cli.rs"
|
path = "src/bin/cli.rs"
|
||||||
|
|
||||||
[target.auth-cli.dependencies]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64ct = { version = "1", features = [ "alloc" ] }
|
base64ct = { version = "1", features = [ "alloc" ] }
|
||||||
build_html = { version = "2" }
|
build_html = { version = "2" }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "flow"
|
name = "result-extended"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
|
@ -33,84 +33,84 @@ use std::{error::Error, fmt};
|
||||||
/// statement.
|
/// statement.
|
||||||
pub trait FatalError: Error {}
|
pub trait FatalError: Error {}
|
||||||
|
|
||||||
/// Flow<A, FE, E> represents a return value that might be a success, might be a fatal error, or
|
/// Result<A, FE, E> represents a return value that might be a success, might be a fatal error, or
|
||||||
/// might be a normal handleable error.
|
/// might be a normal handleable error.
|
||||||
pub enum Flow<A, FE, E> {
|
pub enum Result<A, E, FE> {
|
||||||
/// The operation was successful
|
/// The operation was successful
|
||||||
Ok(A),
|
Ok(A),
|
||||||
|
/// Ordinary errors. These should be handled and the application should recover gracefully.
|
||||||
|
Err(E),
|
||||||
/// The operation encountered a fatal error. These should be bubbled up to a level that can
|
/// The operation encountered a fatal error. These should be bubbled up to a level that can
|
||||||
/// safely shut the application down.
|
/// safely shut the application down.
|
||||||
Fatal(FE),
|
Fatal(FE),
|
||||||
/// Ordinary errors. These should be handled and the application should recover gracefully.
|
|
||||||
Err(E),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, FE, E> Flow<A, FE, E> {
|
impl<A, E, FE> Result<A, E, FE> {
|
||||||
/// Apply an infallible function to a successful value.
|
/// Apply an infallible function to a successful value.
|
||||||
pub fn map<B, O>(self, mapper: O) -> Flow<B, FE, E>
|
pub fn map<B, O>(self, mapper: O) -> Result<B, E, FE>
|
||||||
where
|
where
|
||||||
O: FnOnce(A) -> B,
|
O: FnOnce(A) -> B,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Flow::Ok(val) => Flow::Ok(mapper(val)),
|
Result::Ok(val) => Result::Ok(mapper(val)),
|
||||||
Flow::Fatal(err) => Flow::Fatal(err),
|
Result::Err(err) => Result::Err(err),
|
||||||
Flow::Err(err) => Flow::Err(err),
|
Result::Fatal(err) => Result::Fatal(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a potentially fallible function to a successful value.
|
/// Apply a potentially fallible function to a successful value.
|
||||||
///
|
///
|
||||||
/// Like `Result.and_then`, the mapping function can itself fail.
|
/// Like `Result.and_then`, the mapping function can itself fail.
|
||||||
pub fn and_then<B, O>(self, handler: O) -> Flow<B, FE, E>
|
pub fn and_then<B, O>(self, handler: O) -> Result<B, E, FE>
|
||||||
where
|
where
|
||||||
O: FnOnce(A) -> Flow<B, FE, E>,
|
O: FnOnce(A) -> Result<B, E, FE>,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Flow::Ok(val) => handler(val),
|
Result::Ok(val) => handler(val),
|
||||||
Flow::Fatal(err) => Flow::Fatal(err),
|
Result::Err(err) => Result::Err(err),
|
||||||
Flow::Err(err) => Flow::Err(err),
|
Result::Fatal(err) => Result::Fatal(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map a normal error from one type to another. This is useful for converting an error from
|
/// 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
|
/// 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.
|
/// work with `Result`, so you will likely need to use this a lot.
|
||||||
pub fn map_err<F, O>(self, mapper: O) -> Flow<A, FE, F>
|
pub fn map_err<F, O>(self, mapper: O) -> Result<A, F, FE>
|
||||||
where
|
where
|
||||||
O: FnOnce(E) -> F,
|
O: FnOnce(E) -> F,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Flow::Ok(val) => Flow::Ok(val),
|
Result::Ok(val) => Result::Ok(val),
|
||||||
Flow::Fatal(err) => Flow::Fatal(err),
|
Result::Err(err) => Result::Err(mapper(err)),
|
||||||
Flow::Err(err) => Flow::Err(mapper(err)),
|
Result::Fatal(err) => Result::Fatal(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provide a function to use to recover from (or simply re-throw) an error.
|
/// Provide a function to use to recover from (or simply re-throw) an error.
|
||||||
pub fn or_else<O, F>(self, handler: O) -> Flow<A, FE, F>
|
pub fn or_else<O, F>(self, handler: O) -> Result<A, F, FE>
|
||||||
where
|
where
|
||||||
O: FnOnce(E) -> Flow<A, FE, F>,
|
O: FnOnce(E) -> Result<A, F, FE>,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Flow::Ok(val) => Flow::Ok(val),
|
Result::Ok(val) => Result::Ok(val),
|
||||||
Flow::Fatal(err) => Flow::Fatal(err),
|
Result::Err(err) => handler(err),
|
||||||
Flow::Err(err) => handler(err),
|
Result::Fatal(err) => Result::Fatal(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert from a normal `Result` type to a `Flow` type. The error condition for a `Result` will
|
/// Convert from a normal `Result` type to a `Result` type. The error condition for a `Result` will
|
||||||
/// be treated as `Flow::Err`, never `Flow::Fatal`.
|
/// be treated as `Result::Err`, never `Result::Fatal`.
|
||||||
impl<A, FE, E> From<Result<A, E>> for Flow<A, FE, E> {
|
impl<A, E, FE> From<std::result::Result<A, E>> for Result<A, E, FE> {
|
||||||
fn from(r: Result<A, E>) -> Self {
|
fn from(r: std::result::Result<A, E>) -> Self {
|
||||||
match r {
|
match r {
|
||||||
Ok(val) => Flow::Ok(val),
|
Ok(val) => Result::Ok(val),
|
||||||
Err(err) => Flow::Err(err),
|
Err(err) => Result::Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, FE, E> fmt::Debug for Flow<A, FE, E>
|
impl<A, E, FE> fmt::Debug for Result<A, E, FE>
|
||||||
where
|
where
|
||||||
A: fmt::Debug,
|
A: fmt::Debug,
|
||||||
FE: fmt::Debug,
|
FE: fmt::Debug,
|
||||||
|
@ -118,14 +118,14 @@ where
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Flow::Ok(val) => f.write_fmt(format_args!("Flow::Ok {:?}", val)),
|
Result::Ok(val) => f.write_fmt(format_args!("Result::Ok {:?}", val)),
|
||||||
Flow::Err(err) => f.write_fmt(format_args!("Flow::Err {:?}", err)),
|
Result::Err(err) => f.write_fmt(format_args!("Result::Err {:?}", err)),
|
||||||
Flow::Fatal(err) => f.write_fmt(format_args!("Flow::Fatal {:?}", err)),
|
Result::Fatal(err) => f.write_fmt(format_args!("Result::Fatal {:?}", err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, FE, E> PartialEq for Flow<A, FE, E>
|
impl<A, E, FE> PartialEq for Result<A, E, FE>
|
||||||
where
|
where
|
||||||
A: PartialEq,
|
A: PartialEq,
|
||||||
FE: PartialEq,
|
FE: PartialEq,
|
||||||
|
@ -133,27 +133,51 @@ where
|
||||||
{
|
{
|
||||||
fn eq(&self, rhs: &Self) -> bool {
|
fn eq(&self, rhs: &Self) -> bool {
|
||||||
match (self, rhs) {
|
match (self, rhs) {
|
||||||
(Flow::Ok(val), Flow::Ok(rhs)) => val == rhs,
|
(Result::Ok(val), Result::Ok(rhs)) => val == rhs,
|
||||||
(Flow::Err(_), Flow::Err(_)) => true,
|
(Result::Err(_), Result::Err(_)) => true,
|
||||||
(Flow::Fatal(_), Flow::Fatal(_)) => true,
|
(Result::Fatal(_), Result::Fatal(_)) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<A, E: Error, FE: FatalError> From<std::result::Result<std::result::Result<A, E>, FE>>
|
||||||
|
for Result<A, E, FE>
|
||||||
|
{
|
||||||
|
fn from(res: std::result::Result<std::result::Result<A, E>, FE>) -> Self {
|
||||||
|
match res {
|
||||||
|
Ok(Ok(v)) => ok(v),
|
||||||
|
Ok(Err(e)) => error(e),
|
||||||
|
Err(e) => fatal(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, E: Error, FE: FatalError> From<Result<A, E, FE>>
|
||||||
|
for std::result::Result<std::result::Result<A, E>, FE>
|
||||||
|
{
|
||||||
|
fn from(res: Result<A, E, FE>) -> std::result::Result<std::result::Result<A, E>, FE> {
|
||||||
|
match res {
|
||||||
|
Result::Ok(v) => Ok(Ok(v)),
|
||||||
|
Result::Err(e) => Ok(Err(e)),
|
||||||
|
Result::Fatal(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Convenience function to create an ok value.
|
/// Convenience function to create an ok value.
|
||||||
pub fn ok<A, FE: FatalError, E: Error>(val: A) -> Flow<A, FE, E> {
|
pub fn ok<A, E: Error, FE: FatalError>(val: A) -> Result<A, E, FE> {
|
||||||
Flow::Ok(val)
|
Result::Ok(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function to create an error value.
|
/// Convenience function to create an error value.
|
||||||
pub fn error<A, FE: FatalError, E: Error>(err: E) -> Flow<A, FE, E> {
|
pub fn error<A, E: Error, FE: FatalError>(err: E) -> Result<A, E, FE> {
|
||||||
Flow::Err(err)
|
Result::Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function to create a fatal value.
|
/// Convenience function to create a fatal value.
|
||||||
pub fn fatal<A, FE: FatalError, E: Error>(err: FE) -> Flow<A, FE, E> {
|
pub fn fatal<A, E: Error, FE: FatalError>(err: FE) -> Result<A, E, FE> {
|
||||||
Flow::Fatal(err)
|
Result::Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return early from the current function if the value is a fatal error.
|
/// Return early from the current function if the value is a fatal error.
|
||||||
|
@ -161,9 +185,9 @@ pub fn fatal<A, FE: FatalError, E: Error>(err: FE) -> Flow<A, FE, E> {
|
||||||
macro_rules! return_fatal {
|
macro_rules! return_fatal {
|
||||||
($x:expr) => {
|
($x:expr) => {
|
||||||
match $x {
|
match $x {
|
||||||
Flow::Fatal(err) => return Flow::Fatal(err),
|
Result::Fatal(err) => return Result::Fatal(err),
|
||||||
Flow::Err(err) => Err(err),
|
Result::Err(err) => Err(err),
|
||||||
Flow::Ok(val) => Ok(val),
|
Result::Ok(val) => Ok(val),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -173,9 +197,9 @@ macro_rules! return_fatal {
|
||||||
macro_rules! return_error {
|
macro_rules! return_error {
|
||||||
($x:expr) => {
|
($x:expr) => {
|
||||||
match $x {
|
match $x {
|
||||||
Flow::Ok(val) => val,
|
Result::Ok(val) => val,
|
||||||
Flow::Err(err) => return Flow::Err(err),
|
Result::Err(err) => return Result::Err(err),
|
||||||
Flow::Fatal(err) => return Flow::Fatal(err),
|
Result::Fatal(err) => return Result::Fatal(err),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -210,45 +234,45 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_can_map_things() {
|
fn it_can_map_things() {
|
||||||
let success: Flow<i32, FatalError, Error> = ok(15);
|
let success: Result<i32, Error, FatalError> = ok(15);
|
||||||
assert_eq!(ok(16), success.map(|v| v + 1));
|
assert_eq!(ok(16), success.map(|v| v + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_can_chain_success() {
|
fn it_can_chain_success() {
|
||||||
let success: Flow<i32, FatalError, Error> = ok(15);
|
let success: Result<i32, Error, FatalError> = ok(15);
|
||||||
assert_eq!(ok(16), success.and_then(|v| ok(v + 1)));
|
assert_eq!(ok(16), success.and_then(|v| ok(v + 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_can_handle_an_error() {
|
fn it_can_handle_an_error() {
|
||||||
let failure: Flow<i32, FatalError, Error> = error(Error::Error);
|
let failure: Result<i32, Error, FatalError> = error(Error::Error);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ok::<i32, FatalError, Error>(16),
|
ok::<i32, Error, FatalError>(16),
|
||||||
failure.or_else(|_| ok(16))
|
failure.or_else(|_| ok(16))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn early_exit_on_fatal() {
|
fn early_exit_on_fatal() {
|
||||||
fn ok_func() -> Flow<i32, FatalError, Error> {
|
fn ok_func() -> Result<i32, Error, FatalError> {
|
||||||
let value = return_fatal!(ok::<i32, FatalError, Error>(15));
|
let value = return_fatal!(ok::<i32, Error, FatalError>(15));
|
||||||
match value {
|
match value {
|
||||||
Ok(_) => ok(14),
|
Ok(_) => ok(14),
|
||||||
Err(err) => error(err),
|
Err(err) => error(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn err_func() -> Flow<i32, FatalError, Error> {
|
fn err_func() -> Result<i32, Error, FatalError> {
|
||||||
let value = return_fatal!(error::<i32, FatalError, Error>(Error::Error));
|
let value = return_fatal!(error::<i32, Error, FatalError>(Error::Error));
|
||||||
match value {
|
match value {
|
||||||
Ok(_) => panic!("shouldn't have gotten here"),
|
Ok(_) => panic!("shouldn't have gotten here"),
|
||||||
Err(_) => ok(0),
|
Err(_) => ok(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fatal_func() -> Flow<i32, FatalError, Error> {
|
fn fatal_func() -> Result<i32, Error, FatalError> {
|
||||||
return_fatal!(fatal::<i32, FatalError, Error>(FatalError::FatalError));
|
let _ = return_fatal!(fatal::<i32, Error, FatalError>(FatalError::FatalError));
|
||||||
panic!("failed to bail");
|
panic!("failed to bail");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,19 +283,19 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_can_early_exit_on_all_errors() {
|
fn it_can_early_exit_on_all_errors() {
|
||||||
fn ok_func() -> Flow<i32, FatalError, Error> {
|
fn ok_func() -> Result<i32, Error, FatalError> {
|
||||||
let value = return_error!(ok::<i32, FatalError, Error>(15));
|
let value = return_error!(ok::<i32, Error, FatalError>(15));
|
||||||
assert_eq!(value, 15);
|
assert_eq!(value, 15);
|
||||||
ok(14)
|
ok(14)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn err_func() -> Flow<i32, FatalError, Error> {
|
fn err_func() -> Result<i32, Error, FatalError> {
|
||||||
return_error!(error::<i32, FatalError, Error>(Error::Error));
|
return_error!(error::<i32, Error, FatalError>(Error::Error));
|
||||||
panic!("failed to bail");
|
panic!("failed to bail");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fatal_func() -> Flow<i32, FatalError, Error> {
|
fn fatal_func() -> Result<i32, Error, FatalError> {
|
||||||
return_error!(fatal::<i32, FatalError, Error>(FatalError::FatalError));
|
return_error!(fatal::<i32, Error, FatalError>(FatalError::FatalError));
|
||||||
panic!("failed to bail");
|
panic!("failed to bail");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue