pub mod records; mod sqlite; use miette::{Diagnostic, Result}; use sqlite::SQLiteStore; use thiserror::Error; use records::{EquityChangeEvent, EquitySymbol, FiduciaryAccount}; pub enum Context { SQLite(SQLiteStore), } #[derive(Debug, Diagnostic, Error)] pub enum ContextError { #[error("Data storage error")] DataError(#[from] ::sqlite::Error), #[error("Migration error")] MigrationError(#[from] crate::db::MigrationError), #[error("Int conversion error")] IntConversionError(#[from] std::num::TryFromIntError), #[error("Timestamp conversion error")] ChronoConversionError(#[from] chrono::ParseError), #[error("BigDecimal conversion error")] DecimalConversionError(#[from] bigdecimal::ParseBigDecimalError), #[error("Enum not translatable error")] EnumTranslationError(String), } impl Default for Context { fn default() -> Self { Context::SQLite(SQLiteStore::default()) } } impl std::fmt::Display for Context { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { Self::SQLite(_) => "SQLite", } ) } } pub struct AppContext { context: Context, accounts: Vec, symbols: Vec, events: Vec, } impl Default for AppContext { fn default() -> Self { AppContext { context: Context::default(), // these defaults get overwritten when #init_context is run accounts: vec![], symbols: vec![], events: vec![], } } } pub trait Store { fn init(&mut self) -> Result<(), ContextError>; fn get_accounts(&self) -> Result, ContextError>; fn get_symbols(&self) -> Result, ContextError>; fn get_events(&self) -> Result, ContextError>; fn add_account(&mut self, account: &FiduciaryAccount) -> Result<(), ContextError>; fn add_symbol(&mut self, symbol: &EquitySymbol) -> Result<(), ContextError>; fn add_event(&mut self, event: &EquityChangeEvent) -> Result<(), ContextError>; } impl AppContext { /// Returns a mutable version of the generic implementation of the store /// along with references to the objects in context. Both store and context /// need to be updated in tandem, so a tuple must be returned to be able to /// produce mutable references in the same lifetime fn unwrap_store( &mut self, ) -> ( &mut impl Store, &mut Vec, &mut Vec, &mut Vec, ) { ( match &mut self.context { Context::SQLite(store) => store, }, &mut self.accounts, &mut self.symbols, &mut self.events, ) } fn get_store(&self) -> &impl Store { match &self.context { Context::SQLite(store) => store, } } pub fn init_context(&mut self) -> Result<(), ContextError> { self.unwrap_store().0.init()?; tracing::event!( tracing::Level::DEBUG, context = self.context.to_string(), "Store initialized." ); self.accounts = self.get_store().get_accounts()?; self.symbols = self.get_store().get_symbols()?; self.events = self.get_store().get_events()?; Ok(()) } pub fn get_accounts(&self) -> &Vec { &self.accounts } pub fn get_symbols(&self) -> &Vec { &self.symbols } pub fn get_events(&self) -> &Vec { &self.events } pub fn add_account(&mut self, account: FiduciaryAccount) -> Result<(), ContextError> { let (store, accounts, ..) = self.unwrap_store(); let acc = accounts.push_mut(account); tracing::event!( tracing::Level::TRACE, fiduciary_account_id = acc.fiduciary_account_id, "Added account to context." ); store.add_account(acc)?; Ok(()) } pub fn add_symbol(&mut self, symbol: EquitySymbol) -> Result<(), ContextError> { let (store, _, symbols, ..) = self.unwrap_store(); let sym = symbols.push_mut(symbol); tracing::event!( tracing::Level::TRACE, equity_symbol_id = sym.equity_symbol_id, "Added symbol to context." ); store.add_symbol(sym)?; Ok(()) } pub fn add_event(&mut self, event: EquityChangeEvent) -> Result<(), ContextError> { let (store, .., events) = self.unwrap_store(); let ev = events.push_mut(event); tracing::event!( tracing::Level::TRACE, equity_change_event_id = ev.equity_change_event_id, "Added event to context." ); store.add_event(ev)?; Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn context_default() { let context = Context::default(); let res = match context { Context::SQLite(_) => true, }; assert!(res); } }