refactor(currency_conversion): re frame the currency_conversion crate to make api calls on background thread (#6906)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Prajjwal Kumar
2025-01-28 23:27:23 +05:30
committed by GitHub
parent ecab2b1f51
commit 858866f9f3
9 changed files with 142 additions and 203 deletions

View File

@ -74,13 +74,10 @@ max_feed_count = 200 # The maximum number of frames that will be fe
# This section provides configs for currency conversion api # This section provides configs for currency conversion api
[forex_api] [forex_api]
call_delay = 21600 # Api calls are made after every 6 hrs call_delay = 21600 # Expiration time for data in cache as well as redis in seconds
local_fetch_retry_count = 5 # Fetch from Local cache has retry count as 5 api_key = "" # Api key for making request to foreign exchange Api
local_fetch_retry_delay = 1000 # Retry delay for checking write condition fallback_api_key = "" # Api key for the fallback service
api_timeout = 20000 # Api timeouts once it crosses 20000 ms redis_lock_timeout = 100 # Redis remains write locked for 100 s once the acquire_redis_lock is called
api_key = "YOUR API KEY HERE" # Api key for making request to foreign exchange Api
fallback_api_key = "YOUR API KEY" # Api key for the fallback service
redis_lock_timeout = 26000 # Redis remains write locked for 26000 ms once the acquire_redis_lock is called
# Logging configuration. Logging can be either to file or console or both. # Logging configuration. Logging can be either to file or console or both.

View File

@ -101,13 +101,10 @@ bucket_name = "bucket" # The AWS S3 bucket name for file storage
# This section provides configs for currency conversion api # This section provides configs for currency conversion api
[forex_api] [forex_api]
call_delay = 21600 # Api calls are made after every 6 hrs call_delay = 21600 # Expiration time for data in cache as well as redis in seconds
local_fetch_retry_count = 5 # Fetch from Local cache has retry count as 5 api_key = "" # Api key for making request to foreign exchange Api
local_fetch_retry_delay = 1000 # Retry delay for checking write condition fallback_api_key = "" # Api key for the fallback service
api_timeout = 20000 # Api timeouts once it crosses 20000 ms redis_lock_timeout = 100 # Redis remains write locked for 100 s once the acquire_redis_lock is called
api_key = "YOUR API KEY HERE" # Api key for making request to foreign exchange Api
fallback_api_key = "YOUR API KEY" # Api key for the fallback service
redis_lock_timeout = 26000 # Redis remains write locked for 26000 ms once the acquire_redis_lock is called
[jwekey] # 3 priv/pub key pair [jwekey] # 3 priv/pub key pair
vault_encryption_key = "" # public key in pem format, corresponding private key in rust locker vault_encryption_key = "" # public key in pem format, corresponding private key in rust locker

View File

@ -78,12 +78,9 @@ ttl_for_storage_in_secs = 220752000
[forex_api] [forex_api]
call_delay = 21600 call_delay = 21600
local_fetch_retry_count = 5 api_key = ""
local_fetch_retry_delay = 1000 fallback_api_key = ""
api_timeout = 20000 redis_lock_timeout = 100
api_key = "YOUR API KEY HERE"
fallback_api_key = "YOUR API KEY HERE"
redis_lock_timeout = 26000
[jwekey] [jwekey]
vault_encryption_key = "" vault_encryption_key = ""

View File

@ -31,12 +31,9 @@ pool_size = 5
[forex_api] [forex_api]
call_delay = 21600 call_delay = 21600
local_fetch_retry_count = 5 api_key = ""
local_fetch_retry_delay = 1000 fallback_api_key = ""
api_timeout = 20000 redis_lock_timeout = 100
api_key = "YOUR API KEY HERE"
fallback_api_key = "YOUR API KEY HERE"
redis_lock_timeout = 26000
[replica_database] [replica_database]
username = "db_user" username = "db_user"

View File

@ -115,8 +115,8 @@ To configure the Forex APIs, update the `config/development.toml` or `config/doc
```toml ```toml
[forex_api] [forex_api]
api_key = "YOUR API KEY HERE" # Replace the placeholder with your Primary API Key api_key = ""
fallback_api_key = "YOUR API KEY HERE" # Replace the placeholder with your Fallback API Key fallback_api_key = ""
``` ```
### Important Note ### Important Note
```bash ```bash

View File

@ -303,16 +303,11 @@ pub struct PaymentLink {
#[derive(Debug, Deserialize, Clone, Default)] #[derive(Debug, Deserialize, Clone, Default)]
#[serde(default)] #[serde(default)]
pub struct ForexApi { pub struct ForexApi {
pub local_fetch_retry_count: u64,
pub api_key: Secret<String>, pub api_key: Secret<String>,
pub fallback_api_key: Secret<String>, pub fallback_api_key: Secret<String>,
/// in ms /// in s
pub call_delay: i64, pub call_delay: i64,
/// in ms /// in s
pub local_fetch_retry_delay: u64,
/// in ms
pub api_timeout: u64,
/// in ms
pub redis_lock_timeout: u64, pub redis_lock_timeout: u64,
} }

View File

@ -15,12 +15,7 @@ pub async fn retrieve_forex(
) -> CustomResult<ApplicationResponse<currency::FxExchangeRatesCacheEntry>, ApiErrorResponse> { ) -> CustomResult<ApplicationResponse<currency::FxExchangeRatesCacheEntry>, ApiErrorResponse> {
let forex_api = state.conf.forex_api.get_inner(); let forex_api = state.conf.forex_api.get_inner();
Ok(ApplicationResponse::Json( Ok(ApplicationResponse::Json(
get_forex_rates( get_forex_rates(&state, forex_api.call_delay)
&state,
forex_api.call_delay,
forex_api.local_fetch_retry_delay,
forex_api.local_fetch_retry_count,
)
.await .await
.change_context(ApiErrorResponse::GenericNotFoundError { .change_context(ApiErrorResponse::GenericNotFoundError {
message: "Unable to fetch forex rates".to_string(), message: "Unable to fetch forex rates".to_string(),
@ -53,12 +48,7 @@ pub async fn get_forex_exchange_rates(
state: SessionState, state: SessionState,
) -> CustomResult<ExchangeRates, AnalyticsError> { ) -> CustomResult<ExchangeRates, AnalyticsError> {
let forex_api = state.conf.forex_api.get_inner(); let forex_api = state.conf.forex_api.get_inner();
let rates = get_forex_rates( let rates = get_forex_rates(&state, forex_api.call_delay)
&state,
forex_api.call_delay,
forex_api.local_fetch_retry_delay,
forex_api.local_fetch_retry_count,
)
.await .await
.change_context(AnalyticsError::ForexFetchFailed)?; .change_context(AnalyticsError::ForexFetchFailed)?;

View File

@ -1,4 +1,4 @@
use std::{collections::HashMap, ops::Deref, str::FromStr, sync::Arc, time::Duration}; use std::{collections::HashMap, ops::Deref, str::FromStr, sync::Arc};
use api_models::enums; use api_models::enums;
use common_utils::{date_time, errors::CustomResult, events::ApiEventMetric, ext_traits::AsyncExt}; use common_utils::{date_time, errors::CustomResult, events::ApiEventMetric, ext_traits::AsyncExt};
@ -10,7 +10,8 @@ use redis_interface::DelReply;
use router_env::{instrument, tracing}; use router_env::{instrument, tracing};
use rust_decimal::Decimal; use rust_decimal::Decimal;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use tokio::{sync::RwLock, time::sleep}; use tokio::sync::RwLock;
use tracing_futures::Instrument;
use crate::{ use crate::{
logger, logger,
@ -50,10 +51,14 @@ pub enum ForexCacheError {
CouldNotAcquireLock, CouldNotAcquireLock,
#[error("Provided currency not acceptable")] #[error("Provided currency not acceptable")]
CurrencyNotAcceptable, CurrencyNotAcceptable,
#[error("Forex configuration error: {0}")]
ConfigurationError(String),
#[error("Incorrect entries in default Currency response")] #[error("Incorrect entries in default Currency response")]
DefaultCurrencyParsingError, DefaultCurrencyParsingError,
#[error("Entry not found in cache")] #[error("Entry not found in cache")]
EntryNotFound, EntryNotFound,
#[error("Forex data unavailable")]
ForexDataUnavailable,
#[error("Expiration time invalid")] #[error("Expiration time invalid")]
InvalidLogExpiry, InvalidLogExpiry,
#[error("Error reading local")] #[error("Error reading local")]
@ -107,44 +112,19 @@ impl FxExchangeRatesCacheEntry {
} }
} }
async fn retrieve_forex_from_local() -> Option<FxExchangeRatesCacheEntry> { async fn retrieve_forex_from_local_cache() -> Option<FxExchangeRatesCacheEntry> {
FX_EXCHANGE_RATES_CACHE.read().await.clone() FX_EXCHANGE_RATES_CACHE.read().await.clone()
} }
async fn save_forex_to_local( async fn save_forex_data_to_local_cache(
exchange_rates_cache_entry: FxExchangeRatesCacheEntry, exchange_rates_cache_entry: FxExchangeRatesCacheEntry,
) -> CustomResult<(), ForexCacheError> { ) -> CustomResult<(), ForexCacheError> {
let mut local = FX_EXCHANGE_RATES_CACHE.write().await; let mut local = FX_EXCHANGE_RATES_CACHE.write().await;
*local = Some(exchange_rates_cache_entry); *local = Some(exchange_rates_cache_entry);
logger::debug!("forex_log: forex saved in cache");
Ok(()) Ok(())
} }
// Alternative handler for handling the case, When no data in local as well as redis
#[allow(dead_code)]
async fn waited_fetch_and_update_caches(
state: &SessionState,
local_fetch_retry_delay: u64,
local_fetch_retry_count: u64,
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
for _n in 1..local_fetch_retry_count {
sleep(Duration::from_millis(local_fetch_retry_delay)).await;
//read from redis and update local plus break the loop and return
match retrieve_forex_from_redis(state).await {
Ok(Some(rates)) => {
save_forex_to_local(rates.clone()).await?;
return Ok(rates.clone());
}
Ok(None) => continue,
Err(error) => {
logger::error!(?error);
continue;
}
}
}
//acquire lock one last time and try to fetch and update local & redis
successive_fetch_and_save_forex(state, None).await
}
impl TryFrom<DefaultExchangeRates> for ExchangeRates { impl TryFrom<DefaultExchangeRates> for ExchangeRates {
type Error = error_stack::Report<ForexCacheError>; type Error = error_stack::Report<ForexCacheError>;
fn try_from(value: DefaultExchangeRates) -> Result<Self, Self::Error> { fn try_from(value: DefaultExchangeRates) -> Result<Self, Self::Error> {
@ -178,102 +158,108 @@ impl From<Conversion> for CurrencyFactors {
pub async fn get_forex_rates( pub async fn get_forex_rates(
state: &SessionState, state: &SessionState,
call_delay: i64, call_delay: i64,
local_fetch_retry_delay: u64,
local_fetch_retry_count: u64,
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> { ) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
if let Some(local_rates) = retrieve_forex_from_local().await { if let Some(local_rates) = retrieve_forex_from_local_cache().await {
if local_rates.is_expired(call_delay) { if local_rates.is_expired(call_delay) {
// expired local data // expired local data
handler_local_expired(state, call_delay, local_rates).await logger::debug!("forex_log: Forex stored in cache is expired");
call_forex_api_and_save_data_to_cache_and_redis(state, Some(local_rates)).await
} else { } else {
// Valid data present in local // Valid data present in local
logger::debug!("forex_log: forex found in cache");
Ok(local_rates) Ok(local_rates)
} }
} else { } else {
// No data in local // No data in local
handler_local_no_data( call_api_if_redis_forex_data_expired(state, call_delay).await
state,
call_delay,
local_fetch_retry_delay,
local_fetch_retry_count,
)
.await
} }
} }
async fn handler_local_no_data( async fn call_api_if_redis_forex_data_expired(
state: &SessionState, state: &SessionState,
call_delay: i64, call_delay: i64,
_local_fetch_retry_delay: u64,
_local_fetch_retry_count: u64,
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> { ) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
match retrieve_forex_from_redis(state).await { match retrieve_forex_data_from_redis(state).await {
Ok(Some(data)) => fallback_forex_redis_check(state, data, call_delay).await, Ok(Some(data)) => call_forex_api_if_redis_data_expired(state, data, call_delay).await,
Ok(None) => { Ok(None) => {
// No data in local as well as redis // No data in local as well as redis
Ok(successive_fetch_and_save_forex(state, None).await?) call_forex_api_and_save_data_to_cache_and_redis(state, None).await?;
Err(ForexCacheError::ForexDataUnavailable.into())
} }
Err(error) => { Err(error) => {
logger::error!(?error); // Error in deriving forex rates from redis
Ok(successive_fetch_and_save_forex(state, None).await?) logger::error!("forex_error: {:?}", error);
call_forex_api_and_save_data_to_cache_and_redis(state, None).await?;
Err(ForexCacheError::ForexDataUnavailable.into())
} }
} }
} }
async fn successive_fetch_and_save_forex( async fn call_forex_api_and_save_data_to_cache_and_redis(
state: &SessionState, state: &SessionState,
stale_redis_data: Option<FxExchangeRatesCacheEntry>, stale_redis_data: Option<FxExchangeRatesCacheEntry>,
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> { ) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
match acquire_redis_lock(state).await { // spawn a new thread and do the api fetch and write operations on redis.
Ok(lock_acquired) => { let forex_api_key = state.conf.forex_api.get_inner().api_key.peek();
if !lock_acquired { if forex_api_key.is_empty() {
return stale_redis_data.ok_or(ForexCacheError::CouldNotAcquireLock.into()); Err(ForexCacheError::ConfigurationError("api_keys not provided".into()).into())
} else {
let state = state.clone();
tokio::spawn(
async move {
acquire_redis_lock_and_call_forex_api(&state)
.await
.map_err(|err| {
logger::error!(forex_error=?err);
})
.ok();
} }
let api_rates = fetch_forex_rates(state).await; .in_current_span(),
match api_rates { );
Ok(rates) => successive_save_data_to_redis_local(state, rates).await, stale_redis_data.ok_or(ForexCacheError::EntryNotFound.into())
Err(error) => {
// API not able to fetch data call secondary service
logger::error!(?error);
let secondary_api_rates = fallback_fetch_forex_rates(state).await;
match secondary_api_rates {
Ok(rates) => Ok(successive_save_data_to_redis_local(state, rates).await?),
Err(error) => stale_redis_data.ok_or({
logger::error!(?error);
release_redis_lock(state).await?;
ForexCacheError::ApiUnresponsive.into()
}),
}
}
}
}
Err(error) => stale_redis_data.ok_or({
logger::error!(?error);
ForexCacheError::ApiUnresponsive.into()
}),
} }
} }
async fn successive_save_data_to_redis_local( async fn acquire_redis_lock_and_call_forex_api(
state: &SessionState,
) -> CustomResult<(), ForexCacheError> {
let lock_acquired = acquire_redis_lock(state).await?;
if !lock_acquired {
Err(ForexCacheError::CouldNotAcquireLock.into())
} else {
logger::debug!("forex_log: redis lock acquired");
let api_rates = fetch_forex_rates_from_primary_api(state).await;
match api_rates {
Ok(rates) => save_forex_data_to_cache_and_redis(state, rates).await,
Err(error) => {
logger::error!(forex_error=?error,"primary_forex_error");
// API not able to fetch data call secondary service
let secondary_api_rates = fetch_forex_rates_from_fallback_api(state).await;
match secondary_api_rates {
Ok(rates) => save_forex_data_to_cache_and_redis(state, rates).await,
Err(error) => {
release_redis_lock(state).await?;
Err(error)
}
}
}
}
}
}
async fn save_forex_data_to_cache_and_redis(
state: &SessionState, state: &SessionState,
forex: FxExchangeRatesCacheEntry, forex: FxExchangeRatesCacheEntry,
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> { ) -> CustomResult<(), ForexCacheError> {
Ok(save_forex_to_redis(state, &forex) save_forex_data_to_redis(state, &forex)
.await .await
.async_and_then(|_rates| release_redis_lock(state)) .async_and_then(|_rates| release_redis_lock(state))
.await .await
.async_and_then(|_val| save_forex_to_local(forex.clone())) .async_and_then(|_val| save_forex_data_to_local_cache(forex.clone()))
.await .await
.map_or_else(
|error| {
logger::error!(?error);
forex.clone()
},
|_| forex.clone(),
))
} }
async fn fallback_forex_redis_check( async fn call_forex_api_if_redis_data_expired(
state: &SessionState, state: &SessionState,
redis_data: FxExchangeRatesCacheEntry, redis_data: FxExchangeRatesCacheEntry,
call_delay: i64, call_delay: i64,
@ -282,57 +268,30 @@ async fn fallback_forex_redis_check(
Some(redis_forex) => { Some(redis_forex) => {
// Valid data present in redis // Valid data present in redis
let exchange_rates = FxExchangeRatesCacheEntry::new(redis_forex.as_ref().clone()); let exchange_rates = FxExchangeRatesCacheEntry::new(redis_forex.as_ref().clone());
save_forex_to_local(exchange_rates.clone()).await?; logger::debug!("forex_log: forex response found in redis");
save_forex_data_to_local_cache(exchange_rates.clone()).await?;
Ok(exchange_rates) Ok(exchange_rates)
} }
None => { None => {
// redis expired // redis expired
successive_fetch_and_save_forex(state, Some(redis_data)).await call_forex_api_and_save_data_to_cache_and_redis(state, Some(redis_data)).await
} }
} }
} }
async fn handler_local_expired( async fn fetch_forex_rates_from_primary_api(
state: &SessionState,
call_delay: i64,
local_rates: FxExchangeRatesCacheEntry,
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
match retrieve_forex_from_redis(state).await {
Ok(redis_data) => {
match is_redis_expired(redis_data.as_ref(), call_delay).await {
Some(redis_forex) => {
// Valid data present in redis
let exchange_rates =
FxExchangeRatesCacheEntry::new(redis_forex.as_ref().clone());
save_forex_to_local(exchange_rates.clone()).await?;
Ok(exchange_rates)
}
None => {
// Redis is expired going for API request
successive_fetch_and_save_forex(state, Some(local_rates)).await
}
}
}
Err(error) => {
// data not present in redis waited fetch
logger::error!(?error);
successive_fetch_and_save_forex(state, Some(local_rates)).await
}
}
}
async fn fetch_forex_rates(
state: &SessionState, state: &SessionState,
) -> Result<FxExchangeRatesCacheEntry, error_stack::Report<ForexCacheError>> { ) -> Result<FxExchangeRatesCacheEntry, error_stack::Report<ForexCacheError>> {
let forex_api_key = state.conf.forex_api.get_inner().api_key.peek(); let forex_api_key = state.conf.forex_api.get_inner().api_key.peek();
logger::debug!("forex_log: Primary api call for forex fetch");
let forex_url: String = format!("{}{}{}", FOREX_BASE_URL, forex_api_key, FOREX_BASE_CURRENCY); let forex_url: String = format!("{}{}{}", FOREX_BASE_URL, forex_api_key, FOREX_BASE_CURRENCY);
let forex_request = services::RequestBuilder::new() let forex_request = services::RequestBuilder::new()
.method(services::Method::Get) .method(services::Method::Get)
.url(&forex_url) .url(&forex_url)
.build(); .build();
logger::info!(?forex_request); logger::info!(primary_forex_request=?forex_request,"forex_log: Primary api call for forex fetch");
let response = state let response = state
.api_client .api_client
.send_request( .send_request(
@ -352,7 +311,7 @@ async fn fetch_forex_rates(
"Unable to parse response received from primary api into ForexResponse", "Unable to parse response received from primary api into ForexResponse",
)?; )?;
logger::info!("{:?}", forex_response); logger::info!(primary_forex_response=?forex_response,"forex_log");
let mut conversions: HashMap<enums::Currency, CurrencyFactors> = HashMap::new(); let mut conversions: HashMap<enums::Currency, CurrencyFactors> = HashMap::new();
for enum_curr in enums::Currency::iter() { for enum_curr in enums::Currency::iter() {
@ -361,7 +320,10 @@ async fn fetch_forex_rates(
let from_factor = match Decimal::new(1, 0).checked_div(**rate) { let from_factor = match Decimal::new(1, 0).checked_div(**rate) {
Some(rate) => rate, Some(rate) => rate,
None => { None => {
logger::error!("Rates for {} not received from API", &enum_curr); logger::error!(
"forex_error: Rates for {} not received from API",
&enum_curr
);
continue; continue;
} }
}; };
@ -369,7 +331,10 @@ async fn fetch_forex_rates(
conversions.insert(enum_curr, currency_factors); conversions.insert(enum_curr, currency_factors);
} }
None => { None => {
logger::error!("Rates for {} not received from API", &enum_curr); logger::error!(
"forex_error: Rates for {} not received from API",
&enum_curr
);
} }
}; };
} }
@ -380,7 +345,7 @@ async fn fetch_forex_rates(
))) )))
} }
pub async fn fallback_fetch_forex_rates( pub async fn fetch_forex_rates_from_fallback_api(
state: &SessionState, state: &SessionState,
) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> { ) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
let fallback_forex_api_key = state.conf.forex_api.get_inner().fallback_api_key.peek(); let fallback_forex_api_key = state.conf.forex_api.get_inner().fallback_api_key.peek();
@ -392,7 +357,7 @@ pub async fn fallback_fetch_forex_rates(
.url(&fallback_forex_url) .url(&fallback_forex_url)
.build(); .build();
logger::info!(?fallback_forex_request); logger::info!(fallback_forex_request=?fallback_forex_request,"forex_log: Fallback api call for forex fetch");
let response = state let response = state
.api_client .api_client
.send_request( .send_request(
@ -413,7 +378,8 @@ pub async fn fallback_fetch_forex_rates(
"Unable to parse response received from falback api into ForexResponse", "Unable to parse response received from falback api into ForexResponse",
)?; )?;
logger::info!("{:?}", fallback_forex_response); logger::info!(fallback_forex_response=?fallback_forex_response,"forex_log");
let mut conversions: HashMap<enums::Currency, CurrencyFactors> = HashMap::new(); let mut conversions: HashMap<enums::Currency, CurrencyFactors> = HashMap::new();
for enum_curr in enums::Currency::iter() { for enum_curr in enums::Currency::iter() {
match fallback_forex_response.quotes.get( match fallback_forex_response.quotes.get(
@ -428,7 +394,10 @@ pub async fn fallback_fetch_forex_rates(
let from_factor = match Decimal::new(1, 0).checked_div(**rate) { let from_factor = match Decimal::new(1, 0).checked_div(**rate) {
Some(rate) => rate, Some(rate) => rate,
None => { None => {
logger::error!("Rates for {} not received from API", &enum_curr); logger::error!(
"forex_error: Rates for {} not received from API",
&enum_curr
);
continue; continue;
} }
}; };
@ -441,7 +410,10 @@ pub async fn fallback_fetch_forex_rates(
CurrencyFactors::new(Decimal::new(1, 0), Decimal::new(1, 0)); CurrencyFactors::new(Decimal::new(1, 0), Decimal::new(1, 0));
conversions.insert(enum_curr, currency_factors); conversions.insert(enum_curr, currency_factors);
} else { } else {
logger::error!("Rates for {} not received from API", &enum_curr); logger::error!(
"forex_error: Rates for {} not received from API",
&enum_curr
);
} }
} }
}; };
@ -450,17 +422,18 @@ pub async fn fallback_fetch_forex_rates(
let rates = let rates =
FxExchangeRatesCacheEntry::new(ExchangeRates::new(enums::Currency::USD, conversions)); FxExchangeRatesCacheEntry::new(ExchangeRates::new(enums::Currency::USD, conversions));
match acquire_redis_lock(state).await { match acquire_redis_lock(state).await {
Ok(_) => Ok(successive_save_data_to_redis_local(state, rates).await?), Ok(_) => {
Err(e) => { save_forex_data_to_cache_and_redis(state, rates.clone()).await?;
logger::error!(?e);
Ok(rates) Ok(rates)
} }
Err(e) => Err(e),
} }
} }
async fn release_redis_lock( async fn release_redis_lock(
state: &SessionState, state: &SessionState,
) -> Result<DelReply, error_stack::Report<ForexCacheError>> { ) -> Result<DelReply, error_stack::Report<ForexCacheError>> {
logger::debug!("forex_log: Releasing redis lock");
state state
.store .store
.get_redis_conn() .get_redis_conn()
@ -473,6 +446,7 @@ async fn release_redis_lock(
async fn acquire_redis_lock(state: &SessionState) -> CustomResult<bool, ForexCacheError> { async fn acquire_redis_lock(state: &SessionState) -> CustomResult<bool, ForexCacheError> {
let forex_api = state.conf.forex_api.get_inner(); let forex_api = state.conf.forex_api.get_inner();
logger::debug!("forex_log: Acquiring redis lock");
state state
.store .store
.get_redis_conn() .get_redis_conn()
@ -481,10 +455,7 @@ async fn acquire_redis_lock(state: &SessionState) -> CustomResult<bool, ForexCac
REDIX_FOREX_CACHE_KEY, REDIX_FOREX_CACHE_KEY,
"", "",
Some( Some(
i64::try_from( i64::try_from(forex_api.redis_lock_timeout)
forex_api.local_fetch_retry_count * forex_api.local_fetch_retry_delay
+ forex_api.api_timeout,
)
.change_context(ForexCacheError::ConversionError)?, .change_context(ForexCacheError::ConversionError)?,
), ),
) )
@ -494,10 +465,11 @@ async fn acquire_redis_lock(state: &SessionState) -> CustomResult<bool, ForexCac
.attach_printable("Unable to acquire redis lock") .attach_printable("Unable to acquire redis lock")
} }
async fn save_forex_to_redis( async fn save_forex_data_to_redis(
app_state: &SessionState, app_state: &SessionState,
forex_exchange_cache_entry: &FxExchangeRatesCacheEntry, forex_exchange_cache_entry: &FxExchangeRatesCacheEntry,
) -> CustomResult<(), ForexCacheError> { ) -> CustomResult<(), ForexCacheError> {
logger::debug!("forex_log: Saving forex to redis");
app_state app_state
.store .store
.get_redis_conn() .get_redis_conn()
@ -508,9 +480,10 @@ async fn save_forex_to_redis(
.attach_printable("Unable to save forex data to redis") .attach_printable("Unable to save forex data to redis")
} }
async fn retrieve_forex_from_redis( async fn retrieve_forex_data_from_redis(
app_state: &SessionState, app_state: &SessionState,
) -> CustomResult<Option<FxExchangeRatesCacheEntry>, ForexCacheError> { ) -> CustomResult<Option<FxExchangeRatesCacheEntry>, ForexCacheError> {
logger::debug!("forex_log: Retrieving forex from redis");
app_state app_state
.store .store
.get_redis_conn() .get_redis_conn()
@ -529,6 +502,7 @@ async fn is_redis_expired(
if cache.timestamp + call_delay > date_time::now_unix_timestamp() { if cache.timestamp + call_delay > date_time::now_unix_timestamp() {
Some(cache.data.clone()) Some(cache.data.clone())
} else { } else {
logger::debug!("forex_log: Forex stored in redis is expired");
None None
} }
}) })
@ -542,12 +516,7 @@ pub async fn convert_currency(
from_currency: String, from_currency: String,
) -> CustomResult<api_models::currency::CurrencyConversionResponse, ForexCacheError> { ) -> CustomResult<api_models::currency::CurrencyConversionResponse, ForexCacheError> {
let forex_api = state.conf.forex_api.get_inner(); let forex_api = state.conf.forex_api.get_inner();
let rates = get_forex_rates( let rates = get_forex_rates(&state, forex_api.call_delay)
&state,
forex_api.call_delay,
forex_api.local_fetch_retry_delay,
forex_api.local_fetch_retry_count,
)
.await .await
.change_context(ForexCacheError::ApiError)?; .change_context(ForexCacheError::ApiError)?;

View File

@ -48,12 +48,9 @@ ttl_for_storage_in_secs = 220752000
[forex_api] [forex_api]
call_delay = 21600 call_delay = 21600
local_fetch_retry_count = 5 api_key = ""
local_fetch_retry_delay = 1000 fallback_api_key = ""
api_timeout = 20000 redis_lock_timeout = 100
api_key = "YOUR API KEY HERE"
fallback_api_key = "YOUR API KEY HERE"
redis_lock_timeout = 26000
[eph_key] [eph_key]
validity = 1 validity = 1