refactor(settings): make the function to deserialize hashsets more generic (#3104)

This commit is contained in:
Sanchith Hegde
2024-01-30 14:25:59 +05:30
committed by GitHub
parent a9638d118e
commit 87191d687c
13 changed files with 391 additions and 252 deletions

View File

@ -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());
}
}