use crate::internal::schemas::Schemas;
use crate::internal::utils::extract_deprecated_from_attr;
use crate::internal::{gen_item_ast, gen_open_api_impl};
use crate::openapi_cookie_attr::parse_openapi_cookie_attrs;
use crate::openapi_error_attr::parse_openapi_error_attrs;
use crate::openapi_header_attr::parse_openapi_header_attrs;
use crate::openapi_security_attr::parse_openapi_security_attrs;
use crate::operation_attr::parse_openapi_operation_attrs;
use convert_case::{Case, Casing};
use darling::Error;
use darling::ast::NestedMeta;
use proc_macro::TokenStream;
use proc_macro_error::{OptionExt, abort, proc_macro_error};
use proc_macro2::Span;
use quote::{format_ident, quote};
use syn::{DeriveInput, GenericParam, Ident, ItemFn};
mod internal;
mod openapi_cookie_attr;
mod openapi_error_attr;
mod openapi_header_attr;
mod openapi_security_attr;
mod operation_attr;
const OPENAPI_STRUCT_PREFIX: &str = "__openapi_";
#[proc_macro_error]
#[proc_macro_derive(ApiType)]
pub fn derive_api_type(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as DeriveInput);
let DeriveInput {
attrs: _attrs,
ident,
data: _data,
generics,
vis: _vis,
} = input;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let component_name = quote!(#ident).to_string();
quote!(
#[automatically_derived]
impl #impl_generics schemars::JsonSchema for #ident #ty_generics #where_clause {
fn is_referenceable() -> bool {
false
}
fn schema_name() -> String {
#component_name.to_string()
}
fn json_schema(_generator: &mut schemars::r#gen::SchemaGenerator) -> apistos::Schema {
let instance_type = <Self as TypedSchema>::schema_type();
apistos::Schema::Object(apistos::SchemaObject {
instance_type: Some(apistos::SingleOrVec::Single(Box::new(instance_type))),
format: <Self as TypedSchema>::format(),
..Default::default()
})
}
}
#[automatically_derived]
impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
fn child_schemas() -> Vec<(String, apistos::reference_or::ReferenceOr<apistos::Schema>)> {
vec![]
}
fn schema() -> Option<(String, apistos::reference_or::ReferenceOr<apistos::Schema>)> {
Some((
#component_name.to_string(),
apistos::reference_or::ReferenceOr::Object(apistos::Schema::Object(apistos::SchemaObject {
instance_type: Some(apistos::SingleOrVec::Single(Box::new(<#ident #ty_generics>::schema_type()))),
format: <#ident #ty_generics>::format(),
..Default::default()
}))
))
}
}
)
.into()
}
#[proc_macro_error]
#[proc_macro_derive(ApiComponent)]
pub fn derive_api_component(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as DeriveInput);
let DeriveInput {
attrs: _attrs,
ident,
data: _data,
generics,
vis: _vis,
} = input;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let schema_impl = Schemas { deprecated: false };
quote!(
#[automatically_derived]
impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
#schema_impl
}
)
.into()
}
#[proc_macro_error]
#[proc_macro_derive(ApiSecurity, attributes(openapi_security))]
pub fn derive_api_security(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as DeriveInput);
let DeriveInput {
attrs,
ident,
data: _data,
generics,
vis: _vis,
} = input;
let security_name: String = ident.to_string().to_case(Case::Snake);
let openapi_security_attributes = parse_openapi_security_attrs(&attrs, security_name).expect_or_abort(
"expected #[openapi_security(...)] attribute to be present when used with ApiSecurity derive trait",
);
let security_name = &openapi_security_attributes.name;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote!(
#[automatically_derived]
impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
fn child_schemas() -> Vec<(String, apistos::reference_or::ReferenceOr<apistos::Schema>)> {
vec![]
}
fn schema() -> Option<(String, apistos::reference_or::ReferenceOr<apistos::Schema>)> {
None
}
fn securities() -> std::collections::BTreeMap<String, apistos::security::SecurityScheme> {
#openapi_security_attributes
}
fn security_requirement_name() -> Option<String> {
Some(#security_name.to_string())
}
}
)
.into()
}
#[proc_macro_error]
#[proc_macro_derive(ApiHeader, attributes(openapi_header))]
pub fn derive_api_header(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as DeriveInput);
let DeriveInput {
attrs,
ident,
data: _data,
generics,
vis: _vis,
} = input;
let deprecated = extract_deprecated_from_attr(&attrs);
let openapi_header_attributes = parse_openapi_header_attrs(&attrs, deprecated)
.expect_or_abort("expected #[openapi_header(...)] attribute to be present when used with ApiHeader derive trait");
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let schema_impl = Schemas {
deprecated: openapi_header_attributes.deprecated.unwrap_or_default(),
};
quote!(
#[automatically_derived]
impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
#schema_impl
}
#[automatically_derived]
impl #impl_generics apistos::ApiHeader for #ident #ty_generics #where_clause {
#openapi_header_attributes
}
)
.into()
}
#[proc_macro_error]
#[proc_macro_derive(ApiCookie, attributes(openapi_cookie))]
pub fn derive_api_cookie(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as DeriveInput);
let DeriveInput {
attrs,
ident,
data: _data,
generics,
vis: _vis,
} = input;
let deprecated = extract_deprecated_from_attr(&attrs);
let openapi_cookie_attributes = parse_openapi_cookie_attrs(&attrs, deprecated)
.expect_or_abort("expected #[openapi_cookie(...)] attribute to be present when used with ApiCookie derive trait");
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote!(
#[automatically_derived]
impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
#openapi_cookie_attributes
}
)
.into()
}
#[proc_macro_error]
#[proc_macro_derive(ApiErrorComponent, attributes(openapi_error))]
pub fn derive_api_error(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as DeriveInput);
let DeriveInput {
attrs,
ident,
data: _data,
generics,
vis: _vis,
} = input;
let openapi_error_attributes = parse_openapi_error_attrs(&attrs).expect_or_abort(
"expected #[openapi_error(...)] attribute to be present when used with ApiErrorComponent derive trait",
);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote!(
#[automatically_derived]
impl #impl_generics apistos::ApiErrorComponent for #ident #ty_generics #where_clause {
#openapi_error_attributes
}
)
.into()
}
#[proc_macro_error]
#[proc_macro_attribute]
pub fn api_operation(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr_args = match NestedMeta::parse_meta_list(attr.into()) {
Ok(v) => v,
Err(e) => {
return TokenStream::from(Error::from(e).write_errors());
}
};
let operation_attribute = parse_openapi_operation_attrs(&attr_args);
let default_span = Span::call_site();
let item_ast = match syn::parse::<ItemFn>(item) {
Ok(v) => v,
Err(e) => abort!(e.span(), format!("{e}")),
};
let s_name = format!("{OPENAPI_STRUCT_PREFIX}{}", item_ast.sig.ident);
let openapi_struct = Ident::new(&s_name, default_span);
let generics = &item_ast.sig.generics.clone();
let mut generics_call = quote!();
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let openapi_struct_def = if !generics.params.is_empty() {
let mut generic_types_idents = vec![];
for param in &generics.params {
match param {
GenericParam::Lifetime(_) => {}
GenericParam::Const(_) => {}
GenericParam::Type(_type) => generic_types_idents.push(_type.ident.clone()),
}
}
let turbofish = ty_generics.as_turbofish();
let mut phantom_params = quote!();
let mut phantom_params_names = quote!();
for generic_types_ident in generic_types_idents {
let param_name = Ident::new(
&format_ident!("p_{}", generic_types_ident).to_string().to_lowercase(),
Span::call_site(),
);
phantom_params_names.extend(quote!(#param_name: std::marker::PhantomData,));
phantom_params.extend(quote!(#param_name: std::marker::PhantomData < #generic_types_ident >,))
}
generics_call = quote!(#turbofish { #phantom_params_names });
quote!(struct #openapi_struct #impl_generics #where_clause { #phantom_params })
} else {
quote!(struct #openapi_struct;)
};
let (responder_wrapper, generated_item_ast) =
gen_item_ast(default_span, item_ast, &openapi_struct, &ty_generics, &generics_call);
let generated_item_fn = match syn::parse::<ItemFn>(generated_item_ast.clone().into()) {
Ok(v) => v,
Err(e) => abort!(e.span(), format!("{e}")),
};
let open_api_def = gen_open_api_impl(
&generated_item_fn,
operation_attribute,
&openapi_struct,
&openapi_struct_def,
&impl_generics,
&ty_generics,
where_clause,
&responder_wrapper,
);
quote!(
#open_api_def
#generated_item_ast
)
.into()
}
#[cfg(test)]
use apistos as _;
#[cfg(test)]
use garde as _;
#[cfg(test)]
use schemars as _;
#[cfg(test)]
use serde as _;