mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	refactor(settings): make the function to deserialize hashsets more generic (#3104)
This commit is contained in:
		| @ -1,7 +1,6 @@ | ||||
| use std::{ | ||||
|     collections::{HashMap, HashSet}, | ||||
|     path::PathBuf, | ||||
|     str::FromStr, | ||||
| }; | ||||
|  | ||||
| #[cfg(feature = "olap")] | ||||
| @ -20,7 +19,7 @@ use redis_interface::RedisSettings; | ||||
| pub use router_env::config::{Log, LogConsole, LogFile, LogTelemetry}; | ||||
| use rust_decimal::Decimal; | ||||
| use scheduler::SchedulerSettings; | ||||
| use serde::{de::Error, Deserialize, Deserializer}; | ||||
| use serde::Deserialize; | ||||
| use storage_impl::config::QueueStrategy; | ||||
|  | ||||
| #[cfg(feature = "olap")] | ||||
| @ -191,7 +190,7 @@ pub struct ApplepayMerchantConfigs { | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| pub struct MultipleApiVersionSupportedConnectors { | ||||
|     #[serde(deserialize_with = "connector_deser")] | ||||
|     #[serde(deserialize_with = "deserialize_hashset")] | ||||
|     pub supported_connectors: HashSet<api_models::enums::Connector>, | ||||
| } | ||||
|  | ||||
| @ -205,42 +204,13 @@ pub struct TempLockerEnableConfig(pub HashMap<String, TempLockerEnablePaymentMet | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| pub struct ConnectorCustomer { | ||||
|     #[serde(deserialize_with = "connector_deser")] | ||||
|     #[serde(deserialize_with = "deserialize_hashset")] | ||||
|     pub connector_list: HashSet<api_models::enums::Connector>, | ||||
|     #[cfg(feature = "payouts")] | ||||
|     #[serde(deserialize_with = "payout_connector_deser")] | ||||
|     #[serde(deserialize_with = "deserialize_hashset")] | ||||
|     pub payout_connector_list: HashSet<api_models::enums::PayoutConnectors>, | ||||
| } | ||||
|  | ||||
| fn connector_deser<'a, D>( | ||||
|     deserializer: D, | ||||
| ) -> Result<HashSet<api_models::enums::Connector>, D::Error> | ||||
| where | ||||
|     D: Deserializer<'a>, | ||||
| { | ||||
|     let value = <String>::deserialize(deserializer)?; | ||||
|     Ok(value | ||||
|         .trim() | ||||
|         .split(',') | ||||
|         .flat_map(api_models::enums::Connector::from_str) | ||||
|         .collect()) | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "payouts")] | ||||
| fn payout_connector_deser<'a, D>( | ||||
|     deserializer: D, | ||||
| ) -> Result<HashSet<api_models::enums::PayoutConnectors>, D::Error> | ||||
| where | ||||
|     D: Deserializer<'a>, | ||||
| { | ||||
|     let value = <String>::deserialize(deserializer)?; | ||||
|     Ok(value | ||||
|         .trim() | ||||
|         .split(',') | ||||
|         .flat_map(api_models::enums::PayoutConnectors::from_str) | ||||
|         .collect()) | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "dummy_connector")] | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| pub struct DummyConnector { | ||||
| @ -281,13 +251,13 @@ pub struct SupportedPaymentMethodTypesForMandate( | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone)] | ||||
| pub struct SupportedConnectorsForMandate { | ||||
|     #[serde(deserialize_with = "connector_deser")] | ||||
|     #[serde(deserialize_with = "deserialize_hashset")] | ||||
|     pub connector_list: HashSet<api_models::enums::Connector>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| pub struct PaymentMethodTokenFilter { | ||||
|     #[serde(deserialize_with = "pm_deser")] | ||||
|     #[serde(deserialize_with = "deserialize_hashset")] | ||||
|     pub payment_method: HashSet<diesel_models::enums::PaymentMethod>, | ||||
|     pub payment_method_type: Option<PaymentMethodTypeTokenFilter>, | ||||
|     pub long_lived_token: bool, | ||||
| @ -304,7 +274,7 @@ pub enum ApplePayPreDecryptFlow { | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| pub struct TempLockerEnablePaymentMethodFilter { | ||||
|     #[serde(deserialize_with = "pm_deser")] | ||||
|     #[serde(deserialize_with = "deserialize_hashset")] | ||||
|     pub payment_method: HashSet<diesel_models::enums::PaymentMethod>, | ||||
| } | ||||
|  | ||||
| @ -316,44 +286,14 @@ pub struct TempLockerEnablePaymentMethodFilter { | ||||
|     rename_all = "snake_case" | ||||
| )] | ||||
| pub enum PaymentMethodTypeTokenFilter { | ||||
|     #[serde(deserialize_with = "pm_type_deser")] | ||||
|     #[serde(deserialize_with = "deserialize_hashset")] | ||||
|     EnableOnly(HashSet<diesel_models::enums::PaymentMethodType>), | ||||
|     #[serde(deserialize_with = "pm_type_deser")] | ||||
|     #[serde(deserialize_with = "deserialize_hashset")] | ||||
|     DisableOnly(HashSet<diesel_models::enums::PaymentMethodType>), | ||||
|     #[default] | ||||
|     AllAccepted, | ||||
| } | ||||
|  | ||||
| fn pm_deser<'a, D>( | ||||
|     deserializer: D, | ||||
| ) -> Result<HashSet<diesel_models::enums::PaymentMethod>, D::Error> | ||||
| where | ||||
|     D: Deserializer<'a>, | ||||
| { | ||||
|     let value = <String>::deserialize(deserializer)?; | ||||
|     value | ||||
|         .trim() | ||||
|         .split(',') | ||||
|         .map(diesel_models::enums::PaymentMethod::from_str) | ||||
|         .collect::<Result<_, _>>() | ||||
|         .map_err(D::Error::custom) | ||||
| } | ||||
|  | ||||
| fn pm_type_deser<'a, D>( | ||||
|     deserializer: D, | ||||
| ) -> Result<HashSet<diesel_models::enums::PaymentMethodType>, D::Error> | ||||
| where | ||||
|     D: Deserializer<'a>, | ||||
| { | ||||
|     let value = <String>::deserialize(deserializer)?; | ||||
|     value | ||||
|         .trim() | ||||
|         .split(',') | ||||
|         .map(diesel_models::enums::PaymentMethodType::from_str) | ||||
|         .collect::<Result<_, _>>() | ||||
|         .map_err(D::Error::custom) | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| pub struct BankRedirectConfig( | ||||
|     pub HashMap<api_models::enums::PaymentMethodType, ConnectorBankNames>, | ||||
| @ -363,7 +303,7 @@ pub struct ConnectorBankNames(pub HashMap<String, BanksVector>); | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone)] | ||||
| pub struct BanksVector { | ||||
|     #[serde(deserialize_with = "bank_vec_deser")] | ||||
|     #[serde(deserialize_with = "deserialize_hashset")] | ||||
|     pub banks: HashSet<api_models::enums::BankNames>, | ||||
| } | ||||
|  | ||||
| @ -385,9 +325,9 @@ pub enum PaymentMethodFilterKey { | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| #[serde(default)] | ||||
| pub struct CurrencyCountryFlowFilter { | ||||
|     #[serde(deserialize_with = "currency_set_deser")] | ||||
|     #[serde(deserialize_with = "deserialize_optional_hashset")] | ||||
|     pub currency: Option<HashSet<api_models::enums::Currency>>, | ||||
|     #[serde(deserialize_with = "string_set_deser")] | ||||
|     #[serde(deserialize_with = "deserialize_optional_hashset")] | ||||
|     pub country: Option<HashSet<api_models::enums::CountryAlpha2>>, | ||||
|     pub not_available_flows: Option<NotAvailableFlows>, | ||||
| } | ||||
| @ -416,58 +356,6 @@ pub struct RequiredFieldFinal { | ||||
|     pub common: HashMap<String, RequiredFieldInfo>, | ||||
| } | ||||
|  | ||||
| fn string_set_deser<'a, D>( | ||||
|     deserializer: D, | ||||
| ) -> Result<Option<HashSet<api_models::enums::CountryAlpha2>>, D::Error> | ||||
| where | ||||
|     D: Deserializer<'a>, | ||||
| { | ||||
|     let value = <Option<String>>::deserialize(deserializer)?; | ||||
|     Ok(value.and_then(|inner| { | ||||
|         let list = inner | ||||
|             .trim() | ||||
|             .split(',') | ||||
|             .flat_map(api_models::enums::CountryAlpha2::from_str) | ||||
|             .collect::<HashSet<_>>(); | ||||
|         match list.len() { | ||||
|             0 => None, | ||||
|             _ => Some(list), | ||||
|         } | ||||
|     })) | ||||
| } | ||||
|  | ||||
| fn currency_set_deser<'a, D>( | ||||
|     deserializer: D, | ||||
| ) -> Result<Option<HashSet<api_models::enums::Currency>>, D::Error> | ||||
| where | ||||
|     D: Deserializer<'a>, | ||||
| { | ||||
|     let value = <Option<String>>::deserialize(deserializer)?; | ||||
|     Ok(value.and_then(|inner| { | ||||
|         let list = inner | ||||
|             .trim() | ||||
|             .split(',') | ||||
|             .flat_map(api_models::enums::Currency::from_str) | ||||
|             .collect::<HashSet<_>>(); | ||||
|         match list.len() { | ||||
|             0 => None, | ||||
|             _ => Some(list), | ||||
|         } | ||||
|     })) | ||||
| } | ||||
|  | ||||
| fn bank_vec_deser<'a, D>(deserializer: D) -> Result<HashSet<api_models::enums::BankNames>, D::Error> | ||||
| where | ||||
|     D: Deserializer<'a>, | ||||
| { | ||||
|     let value = <String>::deserialize(deserializer)?; | ||||
|     Ok(value | ||||
|         .trim() | ||||
|         .split(',') | ||||
|         .flat_map(api_models::enums::BankNames::from_str) | ||||
|         .collect()) | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Deserialize, Clone)] | ||||
| #[serde(default)] | ||||
| pub struct Secrets { | ||||
| @ -723,13 +611,13 @@ pub struct ApiKeys { | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| pub struct DelayedSessionConfig { | ||||
|     #[serde(deserialize_with = "deser_to_get_connectors")] | ||||
|     #[serde(deserialize_with = "deserialize_hashset")] | ||||
|     pub connectors_with_delayed_session_response: HashSet<api_models::enums::Connector>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| pub struct WebhookSourceVerificationCall { | ||||
|     #[serde(deserialize_with = "connector_deser")] | ||||
|     #[serde(deserialize_with = "deserialize_hashset")] | ||||
|     pub connectors_with_webhook_source_verification_call: HashSet<api_models::enums::Connector>, | ||||
| } | ||||
|  | ||||
| @ -746,21 +634,6 @@ pub struct ConnectorRequestReferenceIdConfig { | ||||
|     pub merchant_ids_send_payment_id_as_connector_request_id: HashSet<String>, | ||||
| } | ||||
|  | ||||
| fn deser_to_get_connectors<'a, D>( | ||||
|     deserializer: D, | ||||
| ) -> Result<HashSet<api_models::enums::Connector>, D::Error> | ||||
| where | ||||
|     D: Deserializer<'a>, | ||||
| { | ||||
|     let value = <String>::deserialize(deserializer)?; | ||||
|     value | ||||
|         .trim() | ||||
|         .split(',') | ||||
|         .map(api_models::enums::Connector::from_str) | ||||
|         .collect::<Result<_, _>>() | ||||
|         .map_err(D::Error::custom) | ||||
| } | ||||
|  | ||||
| impl Settings { | ||||
|     pub fn new() -> ApplicationResult<Self> { | ||||
|         Self::with_config_path(None) | ||||
| @ -854,24 +727,6 @@ impl Settings { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod payment_method_deserialization_test { | ||||
|     #![allow(clippy::unwrap_used)] | ||||
|     use serde::de::{ | ||||
|         value::{Error as ValueError, StrDeserializer}, | ||||
|         IntoDeserializer, | ||||
|     }; | ||||
|  | ||||
|     use super::*; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_pm_deserializer() { | ||||
|         let deserializer: StrDeserializer<'_, ValueError> = "wallet,card".into_deserializer(); | ||||
|         let test_pm = pm_deser(deserializer); | ||||
|         assert!(test_pm.is_ok()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "payouts")] | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| pub struct Payouts { | ||||
| @ -886,7 +741,7 @@ pub struct LockSettings { | ||||
| } | ||||
|  | ||||
| impl<'de> Deserialize<'de> for LockSettings { | ||||
|     fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { | ||||
|     fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { | ||||
|         #[derive(Deserialize)] | ||||
|         #[serde(deny_unknown_fields)] | ||||
|         struct Inner { | ||||
| @ -921,3 +776,124 @@ pub struct PayPalOnboarding { | ||||
|     pub partner_id: masking::Secret<String>, | ||||
|     pub enabled: bool, | ||||
| } | ||||
|  | ||||
| fn deserialize_hashset_inner<T>(value: impl AsRef<str>) -> Result<HashSet<T>, String> | ||||
| where | ||||
|     T: Eq + std::str::FromStr + std::hash::Hash, | ||||
|     <T as std::str::FromStr>::Err: std::fmt::Display, | ||||
| { | ||||
|     let (values, errors) = value | ||||
|         .as_ref() | ||||
|         .trim() | ||||
|         .split(',') | ||||
|         .map(|s| { | ||||
|             T::from_str(s.trim()).map_err(|error| { | ||||
|                 format!( | ||||
|                     "Unable to deserialize `{}` as `{}`: {error}", | ||||
|                     s.trim(), | ||||
|                     std::any::type_name::<T>() | ||||
|                 ) | ||||
|             }) | ||||
|         }) | ||||
|         .fold( | ||||
|             (HashSet::new(), Vec::new()), | ||||
|             |(mut values, mut errors), result| match result { | ||||
|                 Ok(t) => { | ||||
|                     values.insert(t); | ||||
|                     (values, errors) | ||||
|                 } | ||||
|                 Err(error) => { | ||||
|                     errors.push(error); | ||||
|                     (values, errors) | ||||
|                 } | ||||
|             }, | ||||
|         ); | ||||
|     if !errors.is_empty() { | ||||
|         Err(format!("Some errors occurred:\n{}", errors.join("\n"))) | ||||
|     } else { | ||||
|         Ok(values) | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn deserialize_hashset<'a, D, T>(deserializer: D) -> Result<HashSet<T>, D::Error> | ||||
| where | ||||
|     D: serde::Deserializer<'a>, | ||||
|     T: Eq + std::str::FromStr + std::hash::Hash, | ||||
|     <T as std::str::FromStr>::Err: std::fmt::Display, | ||||
| { | ||||
|     use serde::de::Error; | ||||
|  | ||||
|     deserialize_hashset_inner(<String>::deserialize(deserializer)?).map_err(D::Error::custom) | ||||
| } | ||||
|  | ||||
| fn deserialize_optional_hashset<'a, D, T>(deserializer: D) -> Result<Option<HashSet<T>>, D::Error> | ||||
| where | ||||
|     D: serde::Deserializer<'a>, | ||||
|     T: Eq + std::str::FromStr + std::hash::Hash, | ||||
|     <T as std::str::FromStr>::Err: std::fmt::Display, | ||||
| { | ||||
|     use serde::de::Error; | ||||
|  | ||||
|     <Option<String>>::deserialize(deserializer).map(|value| { | ||||
|         value.map_or(Ok(None), |inner: String| { | ||||
|             let list = deserialize_hashset_inner(inner).map_err(D::Error::custom)?; | ||||
|             match list.len() { | ||||
|                 0 => Ok(None), | ||||
|                 _ => Ok(Some(list)), | ||||
|             } | ||||
|         }) | ||||
|     })? | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod hashset_deserialization_test { | ||||
|     #![allow(clippy::unwrap_used)] | ||||
|     use std::collections::HashSet; | ||||
|  | ||||
|     use serde::de::{ | ||||
|         value::{Error as ValueError, StrDeserializer}, | ||||
|         IntoDeserializer, | ||||
|     }; | ||||
|  | ||||
|     use super::deserialize_hashset; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_payment_method_hashset_deserializer() { | ||||
|         use diesel_models::enums::PaymentMethod; | ||||
|  | ||||
|         let deserializer: StrDeserializer<'_, ValueError> = "wallet,card".into_deserializer(); | ||||
|         let payment_methods = deserialize_hashset::<'_, _, PaymentMethod>(deserializer); | ||||
|         let expected_payment_methods = HashSet::from([PaymentMethod::Wallet, PaymentMethod::Card]); | ||||
|  | ||||
|         assert!(payment_methods.is_ok()); | ||||
|         assert_eq!(payment_methods.unwrap(), expected_payment_methods); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_payment_method_hashset_deserializer_with_spaces() { | ||||
|         use diesel_models::enums::PaymentMethod; | ||||
|  | ||||
|         let deserializer: StrDeserializer<'_, ValueError> = | ||||
|             "wallet, card, bank_debit".into_deserializer(); | ||||
|         let payment_methods = deserialize_hashset::<'_, _, PaymentMethod>(deserializer); | ||||
|         let expected_payment_methods = HashSet::from([ | ||||
|             PaymentMethod::Wallet, | ||||
|             PaymentMethod::Card, | ||||
|             PaymentMethod::BankDebit, | ||||
|         ]); | ||||
|  | ||||
|         assert!(payment_methods.is_ok()); | ||||
|         assert_eq!(payment_methods.unwrap(), expected_payment_methods); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_payment_method_hashset_deserializer_error() { | ||||
|         use diesel_models::enums::PaymentMethod; | ||||
|  | ||||
|         let deserializer: StrDeserializer<'_, ValueError> = | ||||
|             "wallet, card, unknown".into_deserializer(); | ||||
|         let payment_methods = deserialize_hashset::<'_, _, PaymentMethod>(deserializer); | ||||
|  | ||||
|         assert!(payment_methods.is_err()); | ||||
|     } | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Sanchith Hegde
					Sanchith Hegde