use crate::units::Unit;
use fastnum::D128;
use fastnum::decimal::Context;
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::RwLock;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
static CURRENCY_CACHE: RwLock<Option<HashMap<Unit, D128>>> = RwLock::new(None);
pub const BASE_CURRENCY: Unit = Unit::EUR;
#[derive(Deserialize, Debug)]
pub struct CurrencyRate {
#[allow(dead_code)]
pub date: String,
pub base: String,
pub quote: String,
pub rate: serde_json::Number,
}
pub fn set_currency_cache(rates: Vec<CurrencyRate>) -> Result<(), String> {
let mut cache = HashMap::with_capacity(rates.len() + 1);
cache.insert(BASE_CURRENCY, D128::from(1));
for entry in rates {
if entry.base != "EUR" {
return Err("Exchange rate base currency must be EUR".to_string());
}
if let Ok(quote_unit) = currency_code_to_unit(&entry.quote.to_ascii_lowercase()) {
let rate_str = entry.rate.to_string();
let rate = D128::parse_str(&rate_str, Context::default());
cache.insert(quote_unit, rate);
}
}
*CURRENCY_CACHE.write().unwrap() = Some(cache);
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
pub fn initialize_currency_cache() -> Result<(), String> {
if CURRENCY_CACHE.read().unwrap().is_some() {
return Ok(()); }
let rates = fetch_currency_rates()?;
set_currency_cache(rates)?;
Ok(())
}
fn round_to_sig_figs(x: D128, sig_figs: i32) -> D128 {
if x.is_zero() {
return x;
}
let mag: i32 = x.log10().floor().try_into().unwrap();
let shift = sig_figs - 1 - mag;
let factor = D128::TEN.powi(shift);
(x * factor).round(0) / factor
}
pub fn get_exchange_rate(from: Unit, to: Unit) -> Result<D128, String> {
if from == to {
return Ok(D128::from(1));
}
#[cfg(not(target_arch = "wasm32"))]
initialize_currency_cache()?;
let cache = CURRENCY_CACHE.read().unwrap();
let cache = cache.as_ref().unwrap();
let from_rate = cache
.get(&from)
.ok_or_else(|| format!("No exchange rate found for {:?}", from))?;
let to_rate = cache
.get(&to)
.ok_or_else(|| format!("No exchange rate found for {:?}", to))?;
let rate = *to_rate / *from_rate;
let rounded_rate = round_to_sig_figs(rate, 6);
Ok(rounded_rate)
}
#[cfg(not(target_arch = "wasm32"))]
fn fetch_currency_rates() -> Result<Vec<CurrencyRate>, String> {
use reqwest::blocking::get;
let url = "https://api.frankfurter.dev/v2/rates?base=EUR";
let response = get(url).map_err(|e| format!("Failed to fetch currency rates: {}", e))?;
let rates: Vec<CurrencyRate> = response
.json()
.map_err(|e| format!("Failed to parse currency rates: {:?}", e))?;
Ok(rates)
}
pub fn currency_code_to_unit(code: &str) -> Result<Unit, String> {
let unit = match code {
"afn" => Unit::AFN,
"all" => Unit::ALL,
"amd" => Unit::AMD,
"ang" => Unit::ANG,
"aoa" => Unit::AOA,
"ars" => Unit::ARS,
"aud" => Unit::AUD,
"awg" => Unit::AWG,
"azn" => Unit::AZN,
"bam" => Unit::BAM,
"bbd" => Unit::BBD,
"bdt" => Unit::BDT,
"bhd" => Unit::BHD,
"bif" => Unit::BIF,
"bmd" => Unit::BMD,
"bnd" => Unit::BND,
"bob" => Unit::BOB,
"brl" => Unit::BRL,
"bsd" => Unit::BSD,
"btn" => Unit::BTN,
"bwp" => Unit::BWP,
"byn" => Unit::BYN,
"bzd" => Unit::BZD,
"cad" => Unit::CAD,
"cdf" => Unit::CDF,
"chf" => Unit::CHF,
"clp" => Unit::CLP,
"cnh" => Unit::CNH,
"cny" => Unit::CNY,
"cop" => Unit::COP,
"crc" => Unit::CRC,
"cup" => Unit::CUP,
"cve" => Unit::CVE,
"czk" => Unit::CZK,
"djf" => Unit::DJF,
"dkk" => Unit::DKK,
"dop" => Unit::DOP,
"dzd" => Unit::DZD,
"egp" => Unit::EGP,
"ern" => Unit::ERN,
"etb" => Unit::ETB,
"eur" => Unit::EUR,
"fjd" => Unit::FJD,
"fkp" => Unit::FKP,
"gbp" => Unit::GBP,
"gel" => Unit::GEL,
"ggp" => Unit::GGP,
"ghs" => Unit::GHS,
"gip" => Unit::GIP,
"gmd" => Unit::GMD,
"gnf" => Unit::GNF,
"gtq" => Unit::GTQ,
"gyd" => Unit::GYD,
"hkd" => Unit::HKD,
"hnl" => Unit::HNL,
"htg" => Unit::HTG,
"huf" => Unit::HUF,
"idr" => Unit::IDR,
"ils" => Unit::ILS,
"imp" => Unit::IMP,
"inr" => Unit::INR,
"iqd" => Unit::IQD,
"irr" => Unit::IRR,
"isk" => Unit::ISK,
"jep" => Unit::JEP,
"jmd" => Unit::JMD,
"jod" => Unit::JOD,
"jpy" => Unit::JPY,
"kes" => Unit::KES,
"kgs" => Unit::KGS,
"khr" => Unit::KHR,
"kmf" => Unit::KMF,
"kpw" => Unit::KPW,
"krw" => Unit::KRW,
"kwd" => Unit::KWD,
"kyd" => Unit::KYD,
"kzt" => Unit::KZT,
"lak" => Unit::LAK,
"lbp" => Unit::LBP,
"lkr" => Unit::LKR,
"lrd" => Unit::LRD,
"lsl" => Unit::LSL,
"lyd" => Unit::LYD,
"mad" => Unit::MAD,
"mdl" => Unit::MDL,
"mga" => Unit::MGA,
"mkd" => Unit::MKD,
"mmk" => Unit::MMK,
"mnt" => Unit::MNT,
"mop" => Unit::MOP,
"mro" => Unit::MRO,
"mru" => Unit::MRU,
"mur" => Unit::MUR,
"mvr" => Unit::MVR,
"mwk" => Unit::MWK,
"mxn" => Unit::MXN,
"myr" => Unit::MYR,
"mzn" => Unit::MZN,
"nad" => Unit::NAD,
"ngn" => Unit::NGN,
"nio" => Unit::NIO,
"nok" => Unit::NOK,
"npr" => Unit::NPR,
"nzd" => Unit::NZD,
"omr" => Unit::OMR,
"pab" => Unit::PAB,
"pen" => Unit::PEN,
"pgk" => Unit::PGK,
"php" => Unit::PHP,
"pkr" => Unit::PKR,
"pln" => Unit::PLN,
"pyg" => Unit::PYG,
"qar" => Unit::QAR,
"ron" => Unit::RON,
"rsd" => Unit::RSD,
"rub" => Unit::RUB,
"rwf" => Unit::RWF,
"sar" => Unit::SAR,
"sbd" => Unit::SBD,
"scr" => Unit::SCR,
"sdg" => Unit::SDG,
"sek" => Unit::SEK,
"sgd" => Unit::SGD,
"shp" => Unit::SHP,
"sle" => Unit::SLE,
"sos" => Unit::SOS,
"srd" => Unit::SRD,
"ssp" => Unit::SSP,
"stn" => Unit::STN,
"svc" => Unit::SVC,
"syp" => Unit::SYP,
"szl" => Unit::SZL,
"thb" => Unit::THB,
"tjs" => Unit::TJS,
"tmt" => Unit::TMT,
"tnd" => Unit::TND,
"top" => Unit::TOP,
"try" => Unit::TRY,
"ttd" => Unit::TTD,
"twd" => Unit::TWD,
"tzs" => Unit::TZS,
"uah" => Unit::UAH,
"ugx" => Unit::UGX,
"usd" => Unit::USD,
"uyu" => Unit::UYU,
"uzs" => Unit::UZS,
"ves" => Unit::VES,
"vnd" => Unit::VND,
"vuv" => Unit::VUV,
"wst" => Unit::WST,
"xaf" => Unit::XAF,
"xag" => Unit::XAG,
"xau" => Unit::XAU,
"xcd" => Unit::XCD,
"xcg" => Unit::XCG,
"xdr" => Unit::XDR,
"xof" => Unit::XOF,
"xpd" => Unit::XPD,
"xpf" => Unit::XPF,
"xpt" => Unit::XPT,
"yer" => Unit::YER,
"zar" => Unit::ZAR,
"zmw" => Unit::ZMW,
"zwg" => Unit::ZWG,
_ => return Err(format!("Unsupported currency code: {}", code)),
};
Ok(unit)
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn init_currency_cache_with_json(rates_json: &str) -> Result<(), JsValue> {
let rates: Vec<CurrencyRate> = serde_json::from_str(rates_json)
.map_err(|e| JsValue::from_str(&format!("Failed to parse JSON: {}", e)))?;
set_currency_cache(rates)
.map_err(|e| JsValue::from_str(&format!("Failed to parse JSON: {}", e)))?;
Ok(())
}