mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(macro): add config validation macro for connectors (#1755)
This commit is contained in:
@ -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()
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -19,28 +19,13 @@ fn get_inner_path_ident(attribute: &syn::Attribute) -> syn::Result<Vec<syn::Iden
|
||||
.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(
|
||||
input: syn::DeriveInput,
|
||||
) -> syn::Result<proc_macro2::TokenStream> {
|
||||
let schemas_to_create =
|
||||
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))?;
|
||||
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
|
||||
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