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:
Narayan Bhat
2024-01-29 16:20:43 +05:30
committed by GitHub
parent dd0d2dc2dd
commit 7d8d68faba
48 changed files with 6620 additions and 1197 deletions

View File

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

View File

@ -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<_>>();