mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
feat(macro): add config validation macro for connectors (#1755)
This commit is contained in:
@ -423,7 +423,7 @@ pub struct SupportedConnectors {
|
|||||||
pub wallets: Vec<String>,
|
pub wallets: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Default)]
|
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Connectors {
|
pub struct Connectors {
|
||||||
pub aci: ConnectorParams,
|
pub aci: ConnectorParams,
|
||||||
@ -476,26 +476,23 @@ pub struct Connectors {
|
|||||||
pub worldline: ConnectorParams,
|
pub worldline: ConnectorParams,
|
||||||
pub worldpay: ConnectorParams,
|
pub worldpay: ConnectorParams,
|
||||||
pub zen: ConnectorParams,
|
pub zen: ConnectorParams,
|
||||||
|
|
||||||
// Keep this field separate from the remaining fields
|
|
||||||
pub supported: SupportedConnectors,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Default)]
|
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct ConnectorParams {
|
pub struct ConnectorParams {
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
pub secondary_base_url: Option<String>,
|
pub secondary_base_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Default)]
|
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct ConnectorParamsWithMoreUrls {
|
pub struct ConnectorParamsWithMoreUrls {
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
pub base_url_bank_redirects: String,
|
pub base_url_bank_redirects: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Default)]
|
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct ConnectorParamsWithFileUploadUrl {
|
pub struct ConnectorParamsWithFileUploadUrl {
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
@ -503,7 +500,7 @@ pub struct ConnectorParamsWithFileUploadUrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "payouts")]
|
#[cfg(feature = "payouts")]
|
||||||
#[derive(Debug, Deserialize, Clone, Default)]
|
#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct ConnectorParamsWithSecondaryBaseUrl {
|
pub struct ConnectorParamsWithSecondaryBaseUrl {
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
@ -675,7 +672,7 @@ impl Settings {
|
|||||||
}
|
}
|
||||||
self.secrets.validate()?;
|
self.secrets.validate()?;
|
||||||
self.locker.validate()?;
|
self.locker.validate()?;
|
||||||
self.connectors.validate()?;
|
self.connectors.validate("connectors")?;
|
||||||
|
|
||||||
self.scheduler
|
self.scheduler
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@ -129,68 +129,6 @@ impl super::settings::SupportedConnectors {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::settings::Connectors {
|
|
||||||
pub fn validate(&self) -> Result<(), ApplicationError> {
|
|
||||||
self.aci.validate()?;
|
|
||||||
self.adyen.validate()?;
|
|
||||||
self.applepay.validate()?;
|
|
||||||
self.authorizedotnet.validate()?;
|
|
||||||
self.braintree.validate()?;
|
|
||||||
self.checkout.validate()?;
|
|
||||||
self.cybersource.validate()?;
|
|
||||||
self.globalpay.validate()?;
|
|
||||||
self.klarna.validate()?;
|
|
||||||
self.shift4.validate()?;
|
|
||||||
self.stripe.validate()?;
|
|
||||||
self.worldpay.validate()?;
|
|
||||||
|
|
||||||
self.supported.validate()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::settings::ConnectorParams {
|
|
||||||
pub fn validate(&self) -> Result<(), ApplicationError> {
|
|
||||||
common_utils::fp_utils::when(self.base_url.is_default_or_empty(), || {
|
|
||||||
Err(ApplicationError::InvalidConfigurationValueError(
|
|
||||||
"connector base URL must not be empty".into(),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::settings::ConnectorParamsWithFileUploadUrl {
|
|
||||||
pub fn validate(&self) -> Result<(), ApplicationError> {
|
|
||||||
common_utils::fp_utils::when(self.base_url.is_default_or_empty(), || {
|
|
||||||
Err(ApplicationError::InvalidConfigurationValueError(
|
|
||||||
"connector base URL must not be empty".into(),
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
common_utils::fp_utils::when(self.base_url_file_upload.is_default_or_empty(), || {
|
|
||||||
Err(ApplicationError::InvalidConfigurationValueError(
|
|
||||||
"connector file upload base URL must not be empty".into(),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "payouts")]
|
|
||||||
impl super::settings::ConnectorParamsWithSecondaryBaseUrl {
|
|
||||||
pub fn validate(&self) -> Result<(), ApplicationError> {
|
|
||||||
common_utils::fp_utils::when(self.base_url.is_default_or_empty(), || {
|
|
||||||
Err(ApplicationError::InvalidConfigurationValueError(
|
|
||||||
"connector base URL must not be empty".into(),
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
common_utils::fp_utils::when(self.secondary_base_url.is_default_or_empty(), || {
|
|
||||||
Err(ApplicationError::InvalidConfigurationValueError(
|
|
||||||
"connector secondary base URL must not be empty".into(),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::settings::SchedulerSettings {
|
impl super::settings::SchedulerSettings {
|
||||||
pub fn validate(&self) -> Result<(), ApplicationError> {
|
pub fn validate(&self) -> Result<(), ApplicationError> {
|
||||||
use common_utils::fp_utils::when;
|
use common_utils::fp_utils::when;
|
||||||
|
|||||||
@ -509,3 +509,32 @@ pub fn polymorphic_schema(input: proc_macro::TokenStream) -> proc_macro::TokenSt
|
|||||||
.unwrap_or_else(|error| error.into_compile_error())
|
.unwrap_or_else(|error| error.into_compile_error())
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implements the `Validate` trait to check if the config variable is present
|
||||||
|
/// Usage
|
||||||
|
/// ```
|
||||||
|
/// #[derive(ConfigValidate)]
|
||||||
|
/// struct Connectors {
|
||||||
|
/// pub stripe: ConnectorParams,
|
||||||
|
/// pub checkout: ConnectorParams
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This will call the `validate()` function for all the fields in the struct
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// impl Connectors {
|
||||||
|
/// fn validate(&self) -> Result<(), ApplicationError> {
|
||||||
|
/// self.stripe.validate()?;
|
||||||
|
/// self.checkout.validate()?;
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_derive(ConfigValidate)]
|
||||||
|
pub fn validate_config(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let input = syn::parse_macro_input!(input as syn::DeriveInput);
|
||||||
|
|
||||||
|
macros::misc::validate_config(input)
|
||||||
|
.unwrap_or_else(|error| error.into_compile_error())
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
pub(crate) mod api_error;
|
pub(crate) mod api_error;
|
||||||
pub(crate) mod diesel;
|
pub(crate) mod diesel;
|
||||||
pub(crate) mod generate_schema;
|
pub(crate) mod generate_schema;
|
||||||
|
pub(crate) mod misc;
|
||||||
pub(crate) mod operation;
|
pub(crate) mod operation;
|
||||||
|
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|||||||
@ -19,28 +19,13 @@ fn get_inner_path_ident(attribute: &syn::Attribute) -> syn::Result<Vec<syn::Iden
|
|||||||
.collect::<Vec<_>>())
|
.collect::<Vec<_>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_struct_fields(data: syn::Data) -> syn::Result<Punctuated<syn::Field, syn::token::Comma>> {
|
|
||||||
if let syn::Data::Struct(syn::DataStruct {
|
|
||||||
fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
|
|
||||||
..
|
|
||||||
}) = data
|
|
||||||
{
|
|
||||||
Ok(named.to_owned())
|
|
||||||
} else {
|
|
||||||
Err(syn::Error::new(
|
|
||||||
proc_macro2::Span::call_site(),
|
|
||||||
"This macro cannot be used on structs with no fields",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn polymorphic_macro_derive_inner(
|
pub fn polymorphic_macro_derive_inner(
|
||||||
input: syn::DeriveInput,
|
input: syn::DeriveInput,
|
||||||
) -> syn::Result<proc_macro2::TokenStream> {
|
) -> syn::Result<proc_macro2::TokenStream> {
|
||||||
let schemas_to_create =
|
let schemas_to_create =
|
||||||
helpers::get_metadata_inner::<syn::Ident>("generate_schemas", &input.attrs)?;
|
helpers::get_metadata_inner::<syn::Ident>("generate_schemas", &input.attrs)?;
|
||||||
|
|
||||||
let fields = get_struct_fields(input.data)
|
let fields = helpers::get_struct_fields(input.data)
|
||||||
.map_err(|error| syn::Error::new(proc_macro2::Span::call_site(), error))?;
|
.map_err(|error| syn::Error::new(proc_macro2::Span::call_site(), error))?;
|
||||||
|
|
||||||
// Go through all the fields and create a mapping of required fields for a schema
|
// Go through all the fields and create a mapping of required fields for a schema
|
||||||
|
|||||||
@ -35,3 +35,20 @@ pub(super) fn get_metadata_inner<'a, T: Parse + Spanned>(
|
|||||||
Ok(vec)
|
Ok(vec)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_struct_fields(
|
||||||
|
data: syn::Data,
|
||||||
|
) -> syn::Result<Punctuated<syn::Field, syn::token::Comma>> {
|
||||||
|
if let syn::Data::Struct(syn::DataStruct {
|
||||||
|
fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
|
||||||
|
..
|
||||||
|
}) = data
|
||||||
|
{
|
||||||
|
Ok(named.to_owned())
|
||||||
|
} else {
|
||||||
|
Err(syn::Error::new(
|
||||||
|
proc_macro2::Span::call_site(),
|
||||||
|
"This macro cannot be used on structs with no fields",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
63
crates/router_derive/src/macros/misc.rs
Normal file
63
crates/router_derive/src/macros/misc.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
pub fn get_field_type(field_type: syn::Type) -> Option<syn::Ident> {
|
||||||
|
if let syn::Type::Path(path) = field_type {
|
||||||
|
path.path
|
||||||
|
.segments
|
||||||
|
.last()
|
||||||
|
.map(|last_path_segment| last_path_segment.ident.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement the `validate` function for the struct by calling `validate` function on the fields
|
||||||
|
pub fn validate_config(input: syn::DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> {
|
||||||
|
let fields = super::helpers::get_struct_fields(input.data)
|
||||||
|
.map_err(|error| syn::Error::new(proc_macro2::Span::call_site(), error))?;
|
||||||
|
|
||||||
|
let struct_name = input.ident;
|
||||||
|
let function_expansions = fields
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|field| field.ident.to_owned().zip(get_field_type(field.ty)))
|
||||||
|
.filter_map(|(field_ident, field_type_ident)| {
|
||||||
|
// Check if a field is a leaf field, only String ( connector urls ) is supported for now
|
||||||
|
|
||||||
|
let field_ident_string = field_ident.to_string();
|
||||||
|
let is_optional_field = field_type_ident.eq("Option");
|
||||||
|
|
||||||
|
// Do not call validate if it is an optional field
|
||||||
|
if !is_optional_field {
|
||||||
|
let is_leaf_field = field_type_ident.eq("String");
|
||||||
|
let validate_expansion = if is_leaf_field {
|
||||||
|
quote::quote!(common_utils::fp_utils::when(
|
||||||
|
self.#field_ident.is_empty(),
|
||||||
|
|| {
|
||||||
|
Err(ApplicationError::InvalidConfigurationValueError(
|
||||||
|
format!("{} must not be empty for {}", #field_ident_string, parent_field).into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
quote::quote!(
|
||||||
|
self.#field_ident.validate(#field_ident_string)?;
|
||||||
|
)
|
||||||
|
};
|
||||||
|
Some(validate_expansion)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let expansion = quote::quote! {
|
||||||
|
impl #struct_name {
|
||||||
|
pub fn validate(&self, parent_field: &str) -> Result<(), ApplicationError> {
|
||||||
|
#(#function_expansions)*
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(expansion)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user