mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 11:24:45 +08:00
refactor(openapi): move openapi to separate crate to decrease compile times (#3110)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com> Co-authored-by: Sahkal Poddar <sahkalplanet@gmail.com> Co-authored-by: Amisha Prabhat <55580080+Aprabhat19@users.noreply.github.com> Co-authored-by: Sarthak Soni <76486416+Sarthak1799@users.noreply.github.com> Co-authored-by: shashank_attarde <shashank.attarde@juspay.in> Co-authored-by: Aprabhat19 <amishaprabhat@gmail.com> Co-authored-by: sai-harsha-vardhan <harsha111hero@gmail.com> Co-authored-by: Sahkal Poddar <sahkal.poddar@juspay.in> Co-authored-by: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com>
This commit is contained in:
@ -505,7 +505,10 @@ pub fn operation_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
#[proc_macro_derive(PolymorphicSchema, attributes(mandatory_in, generate_schemas))]
|
||||
#[proc_macro_derive(
|
||||
PolymorphicSchema,
|
||||
attributes(mandatory_in, generate_schemas, remove_in)
|
||||
)]
|
||||
pub fn polymorphic_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = syn::parse_macro_input!(input as syn::DeriveInput);
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use syn::{self, parse_quote, punctuated::Punctuated, Token};
|
||||
use syn::{self, parse::Parse, parse_quote, punctuated::Punctuated, Token};
|
||||
|
||||
use crate::macros::helpers;
|
||||
|
||||
@ -19,6 +19,74 @@ fn get_inner_path_ident(attribute: &syn::Attribute) -> syn::Result<Vec<syn::Iden
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Get the type of field
|
||||
fn get_field_type(field_type: syn::Type) -> syn::Result<syn::Ident> {
|
||||
if let syn::Type::Path(path) = field_type {
|
||||
path.path
|
||||
.segments
|
||||
.last()
|
||||
.map(|last_path_segment| last_path_segment.ident.to_owned())
|
||||
.ok_or(syn::Error::new(
|
||||
proc_macro2::Span::call_site(),
|
||||
"Atleast one ident must be specified",
|
||||
))
|
||||
} else {
|
||||
Err(syn::Error::new(
|
||||
proc_macro2::Span::call_site(),
|
||||
"Only path fields are supported",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Get the inner type of option
|
||||
fn get_inner_option_type(field: &syn::Type) -> syn::Result<syn::Ident> {
|
||||
if let syn::Type::Path(ref path) = &field {
|
||||
if let Some(segment) = path.path.segments.last() {
|
||||
if let syn::PathArguments::AngleBracketed(ref args) = &segment.arguments {
|
||||
if let Some(syn::GenericArgument::Type(ty)) = args.args.first() {
|
||||
return get_field_type(ty.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(syn::Error::new(
|
||||
proc_macro2::Span::call_site(),
|
||||
"Only path fields are supported",
|
||||
))
|
||||
}
|
||||
|
||||
mod schema_keyword {
|
||||
use syn::custom_keyword;
|
||||
|
||||
custom_keyword!(schema);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SchemaMeta {
|
||||
struct_name: syn::Ident,
|
||||
type_ident: syn::Ident,
|
||||
}
|
||||
|
||||
/// parse #[mandatory_in(PaymentsCreateRequest = u64)]
|
||||
impl Parse for SchemaMeta {
|
||||
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
|
||||
let struct_name = input.parse::<syn::Ident>()?;
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
let type_ident = input.parse::<syn::Ident>()?;
|
||||
Ok(Self {
|
||||
struct_name,
|
||||
type_ident,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl quote::ToTokens for SchemaMeta {
|
||||
fn to_tokens(&self, _: &mut proc_macro2::TokenStream) {}
|
||||
}
|
||||
|
||||
pub fn polymorphic_macro_derive_inner(
|
||||
input: syn::DeriveInput,
|
||||
) -> syn::Result<proc_macro2::TokenStream> {
|
||||
@ -30,12 +98,21 @@ pub fn polymorphic_macro_derive_inner(
|
||||
|
||||
// Go through all the fields and create a mapping of required fields for a schema
|
||||
// PaymentsCreate -> ["amount","currency"]
|
||||
// This will be stored in a hashset
|
||||
// This will be stored in the hashmap with key as
|
||||
// required_fields -> ((amount, PaymentsCreate), (currency, PaymentsCreate))
|
||||
let mut required_fields = HashSet::<(syn::Ident, syn::Ident)>::new();
|
||||
// and values as the type
|
||||
//
|
||||
// (amount, PaymentsCreate) -> Amount
|
||||
let mut required_fields = HashMap::<(syn::Ident, syn::Ident), syn::Ident>::new();
|
||||
|
||||
// These fields will be removed in the schema
|
||||
// PaymentsUpdate -> ["client_secret"]
|
||||
// This will be stored in a hashset
|
||||
// hide_fields -> ((client_secret, PaymentsUpdate))
|
||||
let mut hide_fields = HashSet::<(syn::Ident, syn::Ident)>::new();
|
||||
let mut all_fields = IndexMap::<syn::Field, Vec<syn::Attribute>>::new();
|
||||
|
||||
fields.iter().for_each(|field| {
|
||||
for field in fields {
|
||||
// Partition the attributes of a field into two vectors
|
||||
// One with #[mandatory_in] attributes present
|
||||
// Rest of the attributes ( include only the schema attribute, serde is not required)
|
||||
@ -44,6 +121,12 @@ pub fn polymorphic_macro_derive_inner(
|
||||
.iter()
|
||||
.partition::<Vec<_>, _>(|attribute| attribute.path().is_ident("mandatory_in"));
|
||||
|
||||
let hidden_fields = field
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|attribute| attribute.path().is_ident("remove_in"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Other attributes ( schema ) are to be printed as is
|
||||
other_attributes
|
||||
.iter()
|
||||
@ -68,7 +151,31 @@ pub fn polymorphic_macro_derive_inner(
|
||||
// ("currency", PaymentsConfirmRequest)
|
||||
//
|
||||
// For these attributes, we need to later add #[schema(required = true)] attribute
|
||||
_ = mandatory_attribute
|
||||
let field_ident = field.ident.ok_or(syn::Error::new(
|
||||
proc_macro2::Span::call_site(),
|
||||
"Cannot use `mandatory_in` on unnamed fields",
|
||||
))?;
|
||||
|
||||
// Parse the #[mandatory_in(PaymentsCreateRequest = u64)] and insert into hashmap
|
||||
// key -> ("amount", PaymentsCreateRequest)
|
||||
// value -> u64
|
||||
if let Some(mandatory_in_attribute) =
|
||||
helpers::get_metadata_inner::<SchemaMeta>("mandatory_in", mandatory_attribute)?.first()
|
||||
{
|
||||
let key = (
|
||||
field_ident.clone(),
|
||||
mandatory_in_attribute.struct_name.clone(),
|
||||
);
|
||||
let value = mandatory_in_attribute.type_ident.clone();
|
||||
required_fields.insert(key, value);
|
||||
}
|
||||
|
||||
// Hidden fields are to be inserted in the Hashset
|
||||
// The hashset will store it in this format
|
||||
// ("client_secret", PaymentsUpdate)
|
||||
//
|
||||
// These fields will not be added to the struct
|
||||
_ = hidden_fields
|
||||
.iter()
|
||||
// Filter only #[mandatory_in] attributes
|
||||
.map(|&attribute| get_inner_path_ident(attribute))
|
||||
@ -76,40 +183,59 @@ pub fn polymorphic_macro_derive_inner(
|
||||
let res = schemas
|
||||
.map_err(|error| syn::Error::new(proc_macro2::Span::call_site(), error))?
|
||||
.iter()
|
||||
.filter_map(|schema| field.ident.to_owned().zip(Some(schema.to_owned())))
|
||||
.map(|schema| (field_ident.clone(), schema.to_owned()))
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
required_fields.extend(res);
|
||||
hide_fields.extend(res);
|
||||
Ok::<_, syn::Error>(())
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// iterate over the schemas and build them with their fields
|
||||
let schemas = schemas_to_create
|
||||
.iter()
|
||||
.map(|schema| {
|
||||
let fields = all_fields
|
||||
.iter()
|
||||
.flat_map(|(field, value)| {
|
||||
let mut attributes = value
|
||||
.iter()
|
||||
.map(|attribute| quote::quote!(#attribute))
|
||||
.collect::<Vec<_>>();
|
||||
.filter_map(|(field, attributes)| {
|
||||
let mut final_attributes = attributes.clone();
|
||||
|
||||
// If the field is required for this schema, then add
|
||||
// #[schema(required = true)] for this field
|
||||
let required_attribute: syn::Attribute =
|
||||
parse_quote!(#[schema(required = true)]);
|
||||
if let Some(field_ident) = field.ident.to_owned() {
|
||||
// If the field is required for this schema, then add
|
||||
// #[schema(value_type = type)] for this field
|
||||
if let Some(required_field_type) =
|
||||
required_fields.get(&(field_ident.clone(), schema.to_owned()))
|
||||
{
|
||||
// This is a required field in the Schema
|
||||
// Add the value type and remove original value type ( if present )
|
||||
let attribute_without_schema_type = attributes
|
||||
.iter()
|
||||
.filter(|attribute| !attribute.path().is_ident("schema"))
|
||||
.map(Clone::clone)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Can be none, because tuple fields have no ident
|
||||
field.ident.to_owned().and_then(|field_ident| {
|
||||
required_fields
|
||||
.contains(&(field_ident, schema.to_owned()))
|
||||
.then(|| attributes.push(quote::quote!(#required_attribute)))
|
||||
});
|
||||
final_attributes = attribute_without_schema_type;
|
||||
|
||||
quote::quote! {
|
||||
#(#attributes)*
|
||||
#field,
|
||||
let value_type_attribute: syn::Attribute =
|
||||
parse_quote!(#[schema(value_type = #required_field_type)]);
|
||||
final_attributes.push(value_type_attribute);
|
||||
}
|
||||
}
|
||||
|
||||
// If the field is to be not shown then
|
||||
let is_hidden_field = field
|
||||
.ident
|
||||
.clone()
|
||||
.map(|field_ident| hide_fields.contains(&(field_ident, schema.to_owned())))
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_hidden_field {
|
||||
None
|
||||
} else {
|
||||
Some(quote::quote! {
|
||||
#(#final_attributes)*
|
||||
#field,
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Reference in New Issue
Block a user