Develop a flow-style implementation of the sled example
This commit is contained in:
parent
7e14f71eaa
commit
ebb28c3ae6
|
@ -760,6 +760,7 @@ dependencies = [
|
||||||
name = "error-training"
|
name = "error-training"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"result-extended",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -15,3 +15,4 @@ path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = { version = "1" }
|
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),
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,11 +1,16 @@
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub mod flow;
|
||||||
|
pub mod sled;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Error, PartialEq)]
|
#[derive(Clone, Debug, Error, PartialEq)]
|
||||||
pub enum FatalError {
|
pub enum FatalError {
|
||||||
#[error("Database corruption detected")]
|
#[error("Database corruption detected")]
|
||||||
DatabaseCorruption,
|
DatabaseCorruption,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ::result_extended::FatalError for FatalError {}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Error, PartialEq)]
|
#[derive(Clone, Debug, Error, PartialEq)]
|
||||||
pub enum MathError {
|
pub enum MathError {
|
||||||
#[error("divide by zero is not defined")]
|
#[error("divide by zero is not defined")]
|
||||||
|
@ -44,77 +49,3 @@ impl From<MathError> for OperationError {
|
||||||
Self::MathError(err)
|
Self::MathError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod sled {
|
|
||||||
//! 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 val = db.get(key)?;
|
|
||||||
|
|
||||||
let res = match val {
|
|
||||||
Err(DatabaseError::NotFound) => Ok(0),
|
|
||||||
Ok(val) => op(val),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(match res {
|
|
||||||
Ok(val) => Ok(val),
|
|
||||||
Err(MathError::ExceedsMaxint) => Ok(127),
|
|
||||||
Err(err) => Err(err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
}))
|
||||||
|
}
|
Loading…
Reference in New Issue