use crate::error::Result;
use crate::models::forex::ForexQuote;
pub(super) fn date_to_timestamp(date: &str) -> Option<i64> {
chrono::NaiveDate::parse_from_str(date, "%Y-%m-%d")
.ok()?
.and_hms_opt(0, 0, 0)
.map(|dt| dt.and_utc().timestamp())
}
pub(super) fn to_quote(from: &str, to: &str, series: &[(String, f64)]) -> Option<ForexQuote> {
let (date, price) = series.last()?;
let previous = series.len().checked_sub(2).and_then(|i| series.get(i));
let change = previous.map(|(_, prev)| price - prev);
let change_percent = previous
.filter(|(_, prev)| *prev != 0.0)
.map(|(_, prev)| (price - prev) / prev * 100.0);
Some(ForexQuote {
symbol: format!("{from}{to}"),
base_currency: Some(from.to_string()),
quote_currency: Some(to.to_string()),
bid: None,
ask: None,
price: Some(*price),
change,
change_percent,
timestamp: date_to_timestamp(date),
})
}
fn identity_quote(currency: &str) -> ForexQuote {
ForexQuote {
symbol: format!("{currency}{currency}"),
base_currency: Some(currency.to_string()),
quote_currency: Some(currency.to_string()),
bid: None,
ask: None,
price: Some(1.0),
change: Some(0.0),
change_percent: Some(0.0),
timestamp: Some(chrono::Utc::now().timestamp()),
}
}
pub(crate) async fn fetch_forex_quote_response(from: &str, to: &str) -> Result<ForexQuote> {
let from = from.trim().to_uppercase();
let to = to.trim().to_uppercase();
if from == to {
return Ok(identity_quote(&from));
}
let series = super::client()?.recent_rates(&from, &to).await?;
to_quote(&from, &to, &series).ok_or_else(|| crate::error::FinanceError::SymbolNotFound {
symbol: Some(format!("{from}{to}")),
context: "Frankfurter returned no usable rate for this pair".to_string(),
})
}