use crate::{
CurrencyError, Result,
api::ExchangeRateService,
conversion::{ConversionRequest, ConversionResult, ConversionType, Currency, Money},
};
#[derive(Debug, Clone)]
pub struct CurrencyConverter {
pub exchange_service: ExchangeRateService,
}
impl CurrencyConverter {
pub fn new(exchange_service: ExchangeRateService) -> Self {
CurrencyConverter { exchange_service }
}
pub fn from_env() -> Result<Self> {
let service = ExchangeRateService::from_env()?;
Ok(CurrencyConverter::new(service))
}
pub async fn convert(&self, request: ConversionRequest) -> Result<ConversionResult> {
if request.is_same_currency() {
return Ok(self.same_currency_conversion(request));
}
if let Ok(result) = self.try_direct_conversion(&request).await {
return Ok(result);
}
self.cross_conversion(&request, "USD").await
}
pub async fn convert_batch(
&self,
requests: Vec<ConversionRequest>,
) -> Vec<Result<ConversionResult>> {
use futures::future::join_all;
let futures = requests.into_iter().map(|req| self.convert(req));
join_all(futures).await
}
fn same_currency_conversion(&self, request: ConversionRequest) -> ConversionResult {
let result_money = Money::new(request.from.amount(), request.to.clone()).unwrap();
ConversionResult::new(
request,
result_money,
1.0, ConversionType::SameCurrency,
)
}
async fn try_direct_conversion(&self, request: &ConversionRequest) -> Result<ConversionResult> {
if let Ok(result) = self
.direct_conversion_with_base(request, request.from.currency().code())
.await
{
return Ok(result);
}
self.direct_conversion_with_base(request, request.to.code())
.await
}
async fn direct_conversion_with_base(
&self,
request: &ConversionRequest,
base_currency: &str,
) -> Result<ConversionResult> {
let rates = self
.exchange_service
.get_latest_rates(base_currency)
.await?;
let exchange_rate = if base_currency == request.from.currency().code() {
rates.get_rate(request.to.code()).ok_or_else(|| {
CurrencyError::api(format!(
"Exchange rate not available for {}",
request.to.code()
))
})?
} else if base_currency == request.to.code() {
let rate_to_base = rates
.get_rate(request.from.currency().code())
.ok_or_else(|| {
CurrencyError::api(format!(
"Exchange rate not available for {}",
request.from.currency().code()
))
})?;
if rate_to_base == 0.0 {
return Err(CurrencyError::conversion(
"Cannot divide by zero exchange rate",
));
}
1.0 / rate_to_base
} else {
return Err(CurrencyError::conversion("Neither currency matches base"));
};
let converted_amount = request.from.amount() * exchange_rate;
let result_money = Money::new(converted_amount, request.to.clone())?;
Ok(ConversionResult::new(
request.clone(),
result_money,
exchange_rate,
ConversionType::Direct,
))
}
async fn cross_conversion(
&self,
request: &ConversionRequest,
via_currency: &str,
) -> Result<ConversionResult> {
let rates = self.exchange_service.get_latest_rates(via_currency).await?;
let from_rate = rates
.get_rate(request.from.currency().code())
.ok_or_else(|| {
CurrencyError::api(format!(
"Exchange rate not available for {}",
request.from.currency().code()
))
})?;
let to_rate = rates.get_rate(request.to.code()).ok_or_else(|| {
CurrencyError::api(format!(
"Exchange rate not available for {}",
request.to.code()
))
})?;
if from_rate == 0.0 {
return Err(CurrencyError::conversion(
"Cannot divide by zero exchange rate",
));
}
let cross_rate = to_rate / from_rate;
let converted_amount = request.from.amount() * cross_rate;
let result_money = Money::new(converted_amount, request.to.clone())?;
Ok(ConversionResult::new(
request.clone(),
result_money,
cross_rate,
ConversionType::Cross {
via_currency: via_currency.to_string(),
},
))
}
pub async fn get_available_currencies(&self) -> Result<Vec<String>> {
let rates = self.exchange_service.get_latest_rates("USD").await?;
let mut currencies = rates.available_currencies();
currencies.push("USD".to_string()); currencies.sort();
Ok(currencies)
}
pub async fn is_currency_supported(&self, currency: &str) -> Result<bool> {
self.exchange_service.is_currency_supported(currency).await
}
pub async fn get_exchange_rate(&self, from: &str, to: &str) -> Result<f64> {
let from_currency = Currency::new(from)?;
let to_currency = Currency::new(to)?;
if from_currency.is_same_as(&to_currency) {
return Ok(1.0);
}
let request = ConversionRequest::from_components(1.0, from, to)?;
let result = self.convert(request).await?;
Ok(result.exchange_rate)
}
}