feat(macro): add config validation macro for connectors (#1755)

This commit is contained in:
Narayan Bhat
2023-07-25 12:42:18 +05:30
committed by GitHub
parent f3baf2ff3f
commit 37a0651660
7 changed files with 117 additions and 87 deletions

View File

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

View File

@ -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;

View File

@ -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

View File

@ -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",
))
}
}

View 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)
}