Bug: refactor(currency_conversion): release redis lock if api call fails



### Description

Refactor currency conversion:
-> For Better error propagation
-> For Redis lock realease, if data isn't fetched from api, so that the other subsequent calls donot fail.
diff --git a/crates/router/src/utils/currency.rs b/crates/router/src/utils/currency.rs
index 2173478ab67..9ab2780da73 100644
--- a/crates/router/src/utils/currency.rs
+++ b/crates/router/src/utils/currency.rs
@@ -7,6 +7,7 @@ use error_stack::ResultExt;
 use masking::PeekInterface;
 use once_cell::sync::Lazy;
 use redis_interface::DelReply;
+use router_env::{instrument, tracing};
 use rust_decimal::Decimal;
 use strum::IntoEnumIterator;
 use tokio::{sync::RwLock, time::sleep};
@@ -150,11 +151,13 @@ impl TryFrom<DefaultExchangeRates> for ExchangeRates {
         let mut conversion_usable: HashMap<enums::Currency, CurrencyFactors> = HashMap::new();
         for (curr, conversion) in value.conversion {
             let enum_curr = enums::Currency::from_str(curr.as_str())
-                .change_context(ForexCacheError::ConversionError)?;
+                .change_context(ForexCacheError::ConversionError)
+                .attach_printable("Unable to Convert currency received")?;
             conversion_usable.insert(enum_curr, CurrencyFactors::from(conversion));
         }
         let base_curr = enums::Currency::from_str(value.base_currency.as_str())
-            .change_context(ForexCacheError::ConversionError)?;
+            .change_context(ForexCacheError::ConversionError)
+            .attach_printable("Unable to convert base currency")?;
         Ok(Self {
             base_currency: base_curr,
             conversion: conversion_usable,
@@ -170,6 +173,8 @@ impl From<Conversion> for CurrencyFactors {
         }
     }
 }
+
+#[instrument(skip_all)]
 pub async fn get_forex_rates(
     state: &SessionState,
     call_delay: i64,
@@ -235,6 +240,7 @@ async fn successive_fetch_and_save_forex(
                         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()
                         }),
                     }
@@ -254,9 +260,9 @@ async fn successive_save_data_to_redis_local(
 ) -> CustomResult<FxExchangeRatesCacheEntry, ForexCacheError> {
     Ok(save_forex_to_redis(state, &forex)
         .await
-        .async_and_then(|_rates| async { release_redis_lock(state).await })
+        .async_and_then(|_rates| release_redis_lock(state))
         .await
-        .async_and_then(|_val| async { Ok(save_forex_to_local(forex.clone()).await) })
+        .async_and_then(|_val| save_forex_to_local(forex.clone()))
         .await
         .map_or_else(
             |error| {
@@ -336,11 +342,15 @@ async fn fetch_forex_rates(
             false,
         )
         .await
-        .change_context(ForexCacheError::ApiUnresponsive)?;
+        .change_context(ForexCacheError::ApiUnresponsive)
+        .attach_printable("Primary forex fetch api unresponsive")?;
     let forex_response = response
         .json::<ForexResponse>()
         .await
-        .change_context(ForexCacheError::ParsingError)?;
+        .change_context(ForexCacheError::ParsingError)
+        .attach_printable(
+            "Unable to parse response received from primary api into ForexResponse",
+        )?;
 
     logger::info!("{:?}", forex_response);
 
@@ -392,11 +402,16 @@ pub async fn fallback_fetch_forex_rates(
             false,
         )
         .await
-        .change_context(ForexCacheError::ApiUnresponsive)?;
+        .change_context(ForexCacheError::ApiUnresponsive)
+        .attach_printable("Fallback forex fetch api unresponsive")?;
+
     let fallback_forex_response = response
         .json::<FallbackForexResponse>()
         .await
-        .change_context(ForexCacheError::ParsingError)?;
+        .change_context(ForexCacheError::ParsingError)
+        .attach_printable(
+            "Unable to parse response received from falback api into ForexResponse",
+        )?;
 
     logger::info!("{:?}", fallback_forex_response);
     let mut conversions: HashMap<enums::Currency, CurrencyFactors> = HashMap::new();
@@ -453,6 +468,7 @@ async fn release_redis_lock(
         .delete_key(REDIX_FOREX_CACHE_KEY)
         .await
         .change_context(ForexCacheError::RedisLockReleaseFailed)
+        .attach_printable("Unable to release redis lock")
 }
 
 async fn acquire_redis_lock(state: &SessionState) -> CustomResult<bool, ForexCacheError> {
@@ -475,6 +491,7 @@ async fn acquire_redis_lock(state: &SessionState) -> CustomResult<bool, ForexCac
         .await
         .map(|val| matches!(val, redis_interface::SetnxReply::KeySet))
         .change_context(ForexCacheError::CouldNotAcquireLock)
+        .attach_printable("Unable to acquire redis lock")
 }
 
 async fn save_forex_to_redis(
@@ -488,6 +505,7 @@ async fn save_forex_to_redis(
         .serialize_and_set_key(REDIX_FOREX_CACHE_DATA, forex_exchange_cache_entry)
         .await
         .change_context(ForexCacheError::RedisWriteError)
+        .attach_printable("Unable to save forex data to redis")
 }
 
 async fn retrieve_forex_from_redis(
@@ -500,6 +518,7 @@ async fn retrieve_forex_from_redis(
         .get_and_deserialize_key(REDIX_FOREX_CACHE_DATA, "FxExchangeRatesCache")
         .await
         .change_context(ForexCacheError::EntryNotFound)
+        .attach_printable("Forex entry not found in redis")
 }
 
 async fn is_redis_expired(
@@ -515,6 +534,7 @@ async fn is_redis_expired(
     })
 }
 
+#[instrument(skip_all)]
 pub async fn convert_currency(
     state: SessionState,
     amount: i64,
@@ -532,14 +552,17 @@ pub async fn convert_currency(
     .change_context(ForexCacheError::ApiError)?;
 
     let to_currency = enums::Currency::from_str(to_currency.as_str())
-        .change_context(ForexCacheError::CurrencyNotAcceptable)?;
+        .change_context(ForexCacheError::CurrencyNotAcceptable)
+        .attach_printable("The provided currency is not acceptable")?;
 
     let from_currency = enums::Currency::from_str(from_currency.as_str())
-        .change_context(ForexCacheError::CurrencyNotAcceptable)?;
+        .change_context(ForexCacheError::CurrencyNotAcceptable)
+        .attach_printable("The provided currency is not acceptable")?;
 
     let converted_amount =
         currency_conversion::conversion::convert(&rates.data, from_currency, to_currency, amount)
-            .change_context(ForexCacheError::ConversionError)?;
+            .change_context(ForexCacheError::ConversionError)
+            .attach_printable("Unable to perform currency conversion")?;
 
     Ok(api_models::currency::CurrencyConversionResponse {
         converted_amount: converted_amount.to_string(),

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [ ] Enhancement
- [x] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
Refactors for currency conversion which invlolves:
-> Better error propagation
-> Redis lock realease, if data isn't fetched from api, so that the other subsequent calls donot fail.

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->
Reqired for analytics

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
Scenarios tested:
1. No api_keys provided: call returned error and redis key was deleted after being set.
2. fallback api_key provided: call returned forex rates and lock was also released.
3. primary api_key provided: call returned forex rates and lock was also released.

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
