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"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"result-extended",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
|
|
|
@ -15,3 +15,4 @@ 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),
|
||||
})
|
||||
}
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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