mmex_lib 0.1.1-beta.1

Library for interacting with Money Manager EX data and logic
Documentation
use rusqlite::Row;
use rust_decimal::prelude::FromPrimitive;
use rust_decimal::Decimal;
use sea_query::{Alias, Expr, Query, SimpleExpr, SqliteQueryBuilder};
use sea_query_rusqlite::RusqliteBinder;
use std::str::FromStr;

use crate::domain::currencies::{Currency, CurrencyError, CurrencyId, CurrencyRepository};
use crate::domain::types::Money;
use crate::infrastructure::db_executor::DbExecutor;
use crate::MmexError;

pub struct CurrencyMapper;

impl CurrencyMapper {
    pub fn map_row(row: &Row) -> rusqlite::Result<Currency> {
        let base_conv_rate = if let Ok(val) = row.get::<_, f64>("BASECONVRATE") {
            Decimal::from_f64(val).unwrap_or(Decimal::ONE)
        } else if let Ok(s) = row.get::<_, String>("BASECONVRATE") {
            Decimal::from_str(&s).unwrap_or(Decimal::ONE)
        } else {
            Decimal::ONE
        };

        Ok(Currency {
            id: CurrencyId::new(row.get("CURRENCYID")?),
            name: row.get("CURRENCYNAME")?,
            pfx_symbol: row.get("PFX_SYMBOL")?,
            sfx_symbol: row.get("SFX_SYMBOL")?,
            decimal_point: row.get("DECIMAL_POINT")?,
            group_separator: row.get("GROUP_SEPARATOR")?,
            unit_name: row.get("UNIT_NAME")?,
            cent_name: row.get("CENT_NAME")?,
            scale: row.get("SCALE")?,
            base_conv_rate: Money::from(base_conv_rate),
            symbol: row.get("CURRENCY_SYMBOL")?,
            currency_type: row.get("CURRENCY_TYPE")?,
        })
    }
}

pub struct SqlCurrencyRepository<'a, E: DbExecutor> {
    executor: &'a E,
}

impl<'a, E: DbExecutor> SqlCurrencyRepository<'a, E> {
    pub fn new(executor: &'a E) -> Self {
        Self { executor }
    }
}

impl<'a, E: DbExecutor> CurrencyRepository for SqlCurrencyRepository<'a, E> {
    fn find_all(&self) -> Result<Vec<Currency>, CurrencyError> {
        let (sql, _) = Query::select()
            .columns([
                "CURRENCYID",
                "CURRENCYNAME",
                "PFX_SYMBOL",
                "SFX_SYMBOL",
                "DECIMAL_POINT",
                "GROUP_SEPARATOR",
                "UNIT_NAME",
                "CENT_NAME",
                "SCALE",
                "BASECONVRATE",
                "CURRENCY_SYMBOL",
                "CURRENCY_TYPE",
            ])
            .from(Alias::new("CURRENCYFORMATS_V1"))
            .build(SqliteQueryBuilder);
        Ok(self
            .executor
            .query_map_ext(&sql, [], |row| CurrencyMapper::map_row(row))?)
    }

    fn find_by_id(&self, id: CurrencyId) -> Result<Option<Currency>, CurrencyError> {
        let (sql, values) = Query::select()
            .columns([
                "CURRENCYID",
                "CURRENCYNAME",
                "PFX_SYMBOL",
                "SFX_SYMBOL",
                "DECIMAL_POINT",
                "GROUP_SEPARATOR",
                "UNIT_NAME",
                "CENT_NAME",
                "SCALE",
                "BASECONVRATE",
                "CURRENCY_SYMBOL",
                "CURRENCY_TYPE",
            ])
            .from(Alias::new("CURRENCYFORMATS_V1"))
            .and_where(Expr::col(Alias::new("CURRENCYID")).eq(id.v1))
            .build_rusqlite(SqliteQueryBuilder);

        match self
            .executor
            .query_row_ext(&sql, &values.as_params()[..], |row| {
                CurrencyMapper::map_row(row)
            }) {
            Ok(curr) => Ok(Some(curr)),
            Err(MmexError::NotFound) => Ok(None),
            Err(e) => Err(CurrencyError::Common(e)),
        }
    }

    fn find_by_symbol(&self, symbol: &str) -> Result<Option<Currency>, CurrencyError> {
        let (sql, values) = Query::select()
            .columns([
                "CURRENCYID",
                "CURRENCYNAME",
                "PFX_SYMBOL",
                "SFX_SYMBOL",
                "DECIMAL_POINT",
                "GROUP_SEPARATOR",
                "UNIT_NAME",
                "CENT_NAME",
                "SCALE",
                "BASECONVRATE",
                "CURRENCY_SYMBOL",
                "CURRENCY_TYPE",
            ])
            .from(Alias::new("CURRENCYFORMATS_V1"))
            .and_where(Expr::col(Alias::new("CURRENCY_SYMBOL")).eq(symbol))
            .build_rusqlite(SqliteQueryBuilder);

        match self
            .executor
            .query_row_ext(&sql, &values.as_params()[..], |row| {
                CurrencyMapper::map_row(row)
            }) {
            Ok(curr) => Ok(Some(curr)),
            Err(MmexError::NotFound) => Ok(None),
            Err(e) => Err(CurrencyError::Common(e)),
        }
    }

    fn insert(&self, c: &Currency) -> Result<Currency, CurrencyError> {
        let (sql, values) = Query::insert()
            .into_table(Alias::new("CURRENCYFORMATS_V1"))
            .columns([
                Alias::new("CURRENCYNAME"),
                Alias::new("PFX_SYMBOL"),
                Alias::new("SFX_SYMBOL"),
                Alias::new("DECIMAL_POINT"),
                Alias::new("GROUP_SEPARATOR"),
                Alias::new("UNIT_NAME"),
                Alias::new("CENT_NAME"),
                Alias::new("SCALE"),
                Alias::new("BASECONVRATE"),
                Alias::new("CURRENCY_SYMBOL"),
                Alias::new("CURRENCY_TYPE"),
            ])
            .values_panic([
                c.name.clone().into(),
                c.pfx_symbol.clone().into(),
                c.sfx_symbol.clone().into(),
                c.decimal_point.clone().into(),
                c.group_separator.clone().into(),
                c.unit_name.clone().into(),
                c.cent_name.clone().into(),
                c.scale.into(),
                c.base_conv_rate.v1.clone().into(),
                c.symbol.clone().into(),
                c.currency_type.clone().into(),
            ])
            .build_rusqlite(SqliteQueryBuilder);

        self.executor.execute_ext(&sql, &values.as_params()[..])?;

        let last_id: i64 = self
            .executor
            .query_row_ext("SELECT last_insert_rowid()", [], |r| r.get(0))?;

        let mut new_curr = c.clone();
        new_curr.id = CurrencyId::new(last_id);
        Ok(new_curr)
    }

    fn update(&self, c: &Currency) -> Result<(), CurrencyError> {
        let (sql, values) = Query::update()
            .table(Alias::new("CURRENCYFORMATS_V1"))
            .values([
                (Alias::new("CURRENCYNAME"), c.name.clone().into()),
                (Alias::new("PFX_SYMBOL"), c.pfx_symbol.clone().into()),
                (Alias::new("SFX_SYMBOL"), c.sfx_symbol.clone().into()),
                (Alias::new("DECIMAL_POINT"), c.decimal_point.clone().into()),
                (
                    Alias::new("GROUP_SEPARATOR"),
                    c.group_separator.clone().into(),
                ),
                (Alias::new("UNIT_NAME"), c.unit_name.clone().into()),
                (Alias::new("CENT_NAME"), c.cent_name.clone().into()),
                (Alias::new("SCALE"), c.scale.into()),
                (
                    Alias::new("BASECONVRATE"),
                    c.base_conv_rate.v1.clone().into(),
                ),
                (Alias::new("CURRENCY_SYMBOL"), c.symbol.clone().into()),
                (Alias::new("CURRENCY_TYPE"), c.currency_type.clone().into()),
            ])
            .and_where(Expr::col(Alias::new("CURRENCYID")).eq(c.id.v1))
            .build_rusqlite(SqliteQueryBuilder);

        self.executor.execute_ext(&sql, &values.as_params()[..])?;
        Ok(())
    }

    fn update_partial(
        &self,
        id: CurrencyId,
        update: crate::domain::currencies::CurrencyUpdate,
    ) -> Result<(), CurrencyError> {
        let mut query = Query::update();
        query.table(Alias::new("CURRENCYFORMATS_V1"));

        let mut has_values = false;

        if let Some(name) = update.name {
            query.value(Alias::new("CURRENCYNAME"), SimpleExpr::from(name));
            has_values = true;
        }
        if let Some(pfx) = update.pfx_symbol {
            query.value(Alias::new("PFX_SYMBOL"), SimpleExpr::from(pfx));
            has_values = true;
        }
        if let Some(sfx) = update.sfx_symbol {
            query.value(Alias::new("SFX_SYMBOL"), SimpleExpr::from(sfx));
            has_values = true;
        }
        if let Some(dp) = update.decimal_point {
            query.value(Alias::new("DECIMAL_POINT"), SimpleExpr::from(dp));
            has_values = true;
        }
        if let Some(gs) = update.group_separator {
            query.value(Alias::new("GROUP_SEPARATOR"), SimpleExpr::from(gs));
            has_values = true;
        }
        if let Some(un) = update.unit_name {
            query.value(Alias::new("UNIT_NAME"), SimpleExpr::from(un));
            has_values = true;
        }
        if let Some(cn) = update.cent_name {
            query.value(Alias::new("CENT_NAME"), SimpleExpr::from(cn));
            has_values = true;
        }
        if let Some(scale) = update.scale {
            query.value(Alias::new("SCALE"), SimpleExpr::from(scale));
            has_values = true;
        }
        if let Some(rate) = update.base_conv_rate {
            query.value(Alias::new("BASECONVRATE"), SimpleExpr::from(rate.v1));
            has_values = true;
        }
        if let Some(symbol) = update.symbol {
            query.value(Alias::new("CURRENCY_SYMBOL"), SimpleExpr::from(symbol));
            has_values = true;
        }
        if let Some(ct) = update.currency_type {
            query.value(Alias::new("CURRENCY_TYPE"), SimpleExpr::from(ct));
            has_values = true;
        }

        if !has_values {
            return Ok(());
        }

        query.and_where(Expr::col(Alias::new("CURRENCYID")).eq(id.v1));

        let (sql, values) = query.build_rusqlite(SqliteQueryBuilder);
        self.executor.execute_ext(&sql, &values.as_params()[..])?;
        Ok(())
    }

    fn delete(&self, id: CurrencyId) -> Result<(), CurrencyError> {
        let (sql, values) = Query::delete()
            .from_table(Alias::new("CURRENCYFORMATS_V1"))
            .and_where(Expr::col(Alias::new("CURRENCYID")).eq(id.v1))
            .build_rusqlite(SqliteQueryBuilder);

        self.executor.execute_ext(&sql, &values.as_params()[..])?;
        Ok(())
    }
}