use derive_more::Deref;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, str::FromStr, sync::LazyLock};
use crate::entities::{Error, Result};
#[derive(Deref, Copy, Clone, PartialEq)]
pub(crate) struct Currency(&'static rusty_money::iso::Currency);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(crate) struct Money(rusty_money::Money<'static, rusty_money::iso::Currency>);
type Decimal = rust_decimal::Decimal;
static CURRENCY_SYMBOLS: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
HashMap::from([
("$", "USD"),
("€", "EUR"),
("£", "GBP"),
("¥", "JPY"),
("₩", "KRW"),
("₹", "INR"),
("₽", "RUB"),
("₺", "TRY"),
("₴", "UAH"),
("₫", "VND"),
("₪", "ILS"),
("₦", "NGN"),
("₱", "PHP"),
("฿", "THB"),
("₡", "CRC"),
("₲", "PYG"),
("₵", "GHS"),
("₭", "LAK"),
("₮", "MNT"),
("₸", "KZT"),
("₼", "AZN"),
("₾", "GEL"),
("₣", "CHF"),
])
});
fn replace_currency_symbol(input: &str) -> String {
if let Some(first) = input.chars().next() {
let symbol = &input[..first.len_utf8()];
if let Some(code) = CURRENCY_SYMBOLS.get(symbol) {
return format!("{}{}", code, &input[first.len_utf8()..]);
}
}
input.to_owned()
}
impl FromStr for Currency {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let s = replace_currency_symbol(s);
Ok(Currency(rusty_money::iso::find(&s).ok_or_else(|| {
Error::CouldNotParseRate(format!("Unknown currency: '{s}'"), s)
})?))
}
}
impl Default for Money {
fn default() -> Self {
Money::from_decimal(0.into(), local_currency())
}
}
impl std::fmt::Display for Money {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl Money {
pub(crate) fn from_decimal(amount: Decimal, currency: Currency) -> Money {
Money(rusty_money::Money::from_decimal(amount, ¤cy))
}
pub(crate) fn zero(currency: Currency) -> Money {
Money(rusty_money::Money::from_minor(0, ¤cy))
}
pub(crate) fn add(self, rhs: Self) -> Result<Money> {
Ok(Money(self.0.add(rhs.0)?))
}
pub(crate) fn mul<N: Into<Decimal>>(&self, n: N) -> Result<Money> {
Ok(Money(self.0.mul(n)?))
}
pub(crate) fn currency(&self) -> Currency {
Currency(self.0.currency())
}
pub(crate) fn is_zero(&self) -> bool {
self.0.is_zero()
}
}
#[allow(unused)]
pub(crate) fn local_currency() -> Currency {
use rusty_money::iso;
use sys_locale::get_locale;
let locale = get_locale().unwrap_or_else(|| "en-US".to_string());
let region = locale.split('-').nth(1).unwrap_or("").to_uppercase();
Currency(match region.as_str() {
"DE" | "AT" | "FR" | "IT" | "ES" | "NL" | "BE" | "PT" | "FI" | "IE" | "GR" | "SK"
| "SI" | "EE" | "LV" | "LT" => iso::EUR,
"US" => iso::USD,
"GB" => iso::GBP,
"CH" => iso::CHF,
"JP" => iso::JPY,
"CN" => iso::CNY,
"CA" => iso::CAD,
"AU" => iso::AUD,
"IN" => iso::INR,
"BR" => iso::BRL,
"PL" => iso::PLN,
"CZ" => iso::CZK,
"SE" => iso::SEK,
"NO" => iso::NOK,
"DK" => iso::DKK,
_ => todo!("currency error"),
})
}