Develop a flow-style implementation of the sled example

This commit is contained in:
Savanni D'Gerinel 2023-10-11 11:28:04 -04:00
parent 7e14f71eaa
commit ebb28c3ae6
5 changed files with 150 additions and 74 deletions

1
Cargo.lock generated
View File

@ -760,6 +760,7 @@ dependencies = [
name = "error-training"
version = "0.1.0"
dependencies = [
"result-extended",
"thiserror",
]

View File

@ -15,3 +15,4 @@ path = "src/main.rs"
[dependencies]
thiserror = { version = "1" }
result-extended = { path = "../result-extended" }

View File

@ -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),
})
}

View File

@ -1,11 +1,16 @@
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")]
@ -44,77 +49,3 @@ impl From<MathError> for OperationError {
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),
})
}
}

View File

@ -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),
}))
}