mod ledger; mod store; use miette::{Diagnostic, ErrReport, Result}; use store::{Context, Store}; use thiserror::Error; #[derive(Debug, Default, PartialEq)] pub enum AppMode { #[default] Init, Counter, Exit, } impl std::fmt::Display for AppMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { AppMode::Init => "INIT", AppMode::Counter => "COUNTER", AppMode::Exit => "EXIT", } ) } } #[derive(Default)] pub struct App { context: Context, error: Option, mode: AppMode, } #[derive(Diagnostic, Debug, Error, PartialEq)] #[error("An overflow error occurred")] #[diagnostic()] pub struct OverflowError { #[help] pub _msg: String, } impl App { /// Build context in App and pass it forward pub fn init_context(&mut self) -> Result<&mut Self> { let store = self.context.unwrap_store(); store.init()?; self.mode = AppMode::Counter; Ok(self) } pub fn get_app_mode(&self) -> &AppMode { &self.mode } pub fn get_exit_status(&self) -> bool { self.mode == AppMode::Exit } pub fn exit(&mut self) -> Result<()> { tracing::event!(tracing::Level::TRACE, exit = true, "Changing exit status."); self.mode = AppMode::Exit; Ok(()) } pub fn decrement_counter(&mut self) -> Result<()> { let store = self.context.unwrap_store(); let count = store.get_counter()?; tracing::event!(tracing::Level::TRACE, count, "Decrementing counter."); if count == 0 { Err(OverflowError { _msg: "Cannot count below 0".into(), } .into()) } else { store.set_counter(count - 1)?; Ok(()) } } pub fn increment_counter(&mut self) -> Result<()> { let store = self.context.unwrap_store(); let count = store.get_counter()?; tracing::event!(tracing::Level::TRACE, count, "Incrementing counter."); if count == u8::MAX { Err(OverflowError { _msg: "Cannot count above u8::MAX".into(), } .into()) } else { store.set_counter(count + 1)?; Ok(()) } } pub fn display_counter(&self) -> String { self.context .get_store() .get_counter() .map(|u| u.to_string()) .unwrap_or("Error".into()) } pub fn set_error(&mut self, error: ErrReport) -> Result<()> { tracing::event!( tracing::Level::ERROR, error = error.to_string(), help = error.help().map_or(String::default(), |h| h.to_string()) ); self.error = Some(error); Ok(()) } pub fn get_error(&self) -> &Option { &self.error } /// Clears any existing input error in the app state pub fn clear_error(&mut self) -> Result<()> { self.error = None; Ok(()) } } #[cfg(test)] mod tests { use super::*; use store::ContextError; fn get_count(app: &App) -> Result { app.context.get_store().get_counter() } #[test] fn display_counter_inited() { let mut app = App::default(); assert!(app.init_context().is_ok()); let count = app.display_counter(); assert_eq!(count, "0"); } #[test] fn display_counter_noninited() { let app = App::default(); let count = app.display_counter(); assert_eq!(count, "Error"); } #[test] fn increment_decrement() { let mut app = App::default(); assert!(app.init_context().is_ok()); let res = app.increment_counter(); assert!(res.is_ok()); assert_eq!(get_count(&app).unwrap(), 1); let res = app.decrement_counter(); assert!(res.is_ok()); assert_eq!(get_count(&app).unwrap(), 0); } #[test] fn increment_n() { let mut app = App::default(); assert!(app.init_context().is_ok()); let n = 8; for i in 1..=n { let res = app.increment_counter(); assert!(res.is_ok()); assert_eq!(get_count(&app).unwrap(), i); } assert_eq!(get_count(&app).unwrap(), n); } #[test] fn increment_n_decrement() { let mut app = App::default(); assert!(app.init_context().is_ok()); let n = 8; for i in 1..=n { let res = app.increment_counter(); assert!(res.is_ok()); assert_eq!(get_count(&app).unwrap(), i); } assert_eq!(get_count(&app).unwrap(), n); let res = app.decrement_counter(); assert!(res.is_ok()); assert_eq!(get_count(&app).unwrap(), n - 1); } #[test] fn exit() { let mut app = App::default(); assert!(app.init_context().is_ok()); let res = app.exit(); assert!(res.is_ok()); assert_eq!(app.mode, AppMode::Exit); } #[test] fn decrement_counter_error() { let mut app = App::default(); assert!(app.init_context().is_ok()); let res = app.decrement_counter(); assert!(res.is_err()); let res_err = res.unwrap_err(); let casted_val = res_err .downcast_ref::() .expect("something bad occurred"); let expected_err_msg: String = "Cannot count below 0".into(); assert_eq!(casted_val._msg, expected_err_msg); assert_eq!( *casted_val, OverflowError { _msg: expected_err_msg, }, ); } #[test] fn increment_counter_error() { let mut app = App::default(); assert!(app.init_context().is_ok()); assert!(app.context.unwrap_store().set_counter(u8::MAX).is_ok()); let res = app.increment_counter(); assert!(res.is_err()); let res_err = res.unwrap_err(); let casted_val = res_err .downcast_ref::() .expect("something bad occurred"); let expected_err_msg: String = "Cannot count above u8::MAX".into(); assert_eq!(casted_val._msg, expected_err_msg); assert_eq!( *casted_val, OverflowError { _msg: expected_err_msg, }, ); } }