diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index f93d59ef16..f1e611ebd8 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -423,7 +423,7 @@ pub struct SupportedConnectors { pub wallets: Vec, } -#[derive(Debug, Deserialize, Clone, Default)] +#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] #[serde(default)] pub struct Connectors { pub aci: ConnectorParams, @@ -476,26 +476,23 @@ pub struct Connectors { pub worldline: ConnectorParams, pub worldpay: 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)] pub struct ConnectorParams { pub base_url: String, pub secondary_base_url: Option, } -#[derive(Debug, Deserialize, Clone, Default)] +#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] #[serde(default)] pub struct ConnectorParamsWithMoreUrls { pub base_url: String, pub base_url_bank_redirects: String, } -#[derive(Debug, Deserialize, Clone, Default)] +#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] #[serde(default)] pub struct ConnectorParamsWithFileUploadUrl { pub base_url: String, @@ -503,7 +500,7 @@ pub struct ConnectorParamsWithFileUploadUrl { } #[cfg(feature = "payouts")] -#[derive(Debug, Deserialize, Clone, Default)] +#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] #[serde(default)] pub struct ConnectorParamsWithSecondaryBaseUrl { pub base_url: String, @@ -675,7 +672,7 @@ impl Settings { } self.secrets.validate()?; self.locker.validate()?; - self.connectors.validate()?; + self.connectors.validate("connectors")?; self.scheduler .as_ref() diff --git a/crates/router/src/configs/validations.rs b/crates/router/src/configs/validations.rs index 8efbd7f313..2ee79f35c9 100644 --- a/crates/router/src/configs/validations.rs +++ b/crates/router/src/configs/validations.rs @@ -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 { pub fn validate(&self) -> Result<(), ApplicationError> { use common_utils::fp_utils::when; diff --git a/crates/router_derive/src/lib.rs b/crates/router_derive/src/lib.rs index 789505b66f..58d623682a 100644 --- a/crates/router_derive/src/lib.rs +++ b/crates/router_derive/src/lib.rs @@ -509,3 +509,32 @@ pub fn polymorphic_schema(input: proc_macro::TokenStream) -> proc_macro::TokenSt .unwrap_or_else(|error| error.into_compile_error()) .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() +} diff --git a/crates/router_derive/src/macros.rs b/crates/router_derive/src/macros.rs index b451eb972e..6f5de38330 100644 --- a/crates/router_derive/src/macros.rs +++ b/crates/router_derive/src/macros.rs @@ -1,6 +1,7 @@ pub(crate) mod api_error; pub(crate) mod diesel; pub(crate) mod generate_schema; +pub(crate) mod misc; pub(crate) mod operation; mod helpers; diff --git a/crates/router_derive/src/macros/generate_schema.rs b/crates/router_derive/src/macros/generate_schema.rs index 180760b907..ecf9aa431e 100644 --- a/crates/router_derive/src/macros/generate_schema.rs +++ b/crates/router_derive/src/macros/generate_schema.rs @@ -19,28 +19,13 @@ fn get_inner_path_ident(attribute: &syn::Attribute) -> syn::Result>()) } -fn get_struct_fields(data: syn::Data) -> syn::Result> { - 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( input: syn::DeriveInput, ) -> syn::Result { let schemas_to_create = helpers::get_metadata_inner::("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))?; // Go through all the fields and create a mapping of required fields for a schema diff --git a/crates/router_derive/src/macros/helpers.rs b/crates/router_derive/src/macros/helpers.rs index 8932edbe0c..94005453f8 100644 --- a/crates/router_derive/src/macros/helpers.rs +++ b/crates/router_derive/src/macros/helpers.rs @@ -35,3 +35,20 @@ pub(super) fn get_metadata_inner<'a, T: Parse + Spanned>( Ok(vec) }) } + +pub(super) fn get_struct_fields( + data: syn::Data, +) -> syn::Result> { + 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", + )) + } +} diff --git a/crates/router_derive/src/macros/misc.rs b/crates/router_derive/src/macros/misc.rs new file mode 100644 index 0000000000..4717fe730d --- /dev/null +++ b/crates/router_derive/src/macros/misc.rs @@ -0,0 +1,63 @@ +pub fn get_field_type(field_type: syn::Type) -> Option { + 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 { + 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::>(); + + let expansion = quote::quote! { + impl #struct_name { + pub fn validate(&self, parent_field: &str) -> Result<(), ApplicationError> { + #(#function_expansions)* + Ok(()) + } + } + }; + + Ok(expansion) +}