aboutsummaryrefslogtreecommitdiff
path: root/src/app/store/mod.rs
blob: fb6e90f50e50a136bc151a738ae2648d11bdd6b6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
pub mod records;
mod sqlite;

use miette::Diagnostic;
use sqlite::SQLiteStore;
use thiserror::Error;

use records::{EquityChangeEvent, EquitySymbol, FiduciaryAccount};

type Result<T> = miette::Result<T, ContextError>;

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<FiduciaryAccount>,
    symbols: Vec<EquitySymbol>,
    events: Vec<EquityChangeEvent>,
}

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<()>;
    fn get_accounts(&self) -> Result<Vec<FiduciaryAccount>>;
    fn get_symbols(&self) -> Result<Vec<EquitySymbol>>;
    fn get_events(&self) -> Result<Vec<EquityChangeEvent>>;
    fn add_account(&mut self, account: &FiduciaryAccount) -> Result<()>;
    fn add_symbol(&mut self, symbol: &EquitySymbol) -> Result<()>;
    fn add_event(&mut self, event: &EquityChangeEvent) -> Result<()>;
}

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<FiduciaryAccount>,
        &mut Vec<EquitySymbol>,
        &mut Vec<EquityChangeEvent>,
    ) {
        (
            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<()> {
        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<FiduciaryAccount> {
        &self.accounts
    }

    pub fn get_symbols(&self) -> &Vec<EquitySymbol> {
        &self.symbols
    }

    pub fn get_events(&self) -> &Vec<EquityChangeEvent> {
        &self.events
    }

    pub fn add_account(&mut self, account: FiduciaryAccount) -> Result<()> {
        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<()> {
        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<()> {
        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);
    }
}