apistos_gen/
lib.rs

1//! A set of macro utilities to generate [OpenAPI v3.0.3](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md) documentation from Rust models.
2//!
3//! ⚠️ This crate is not indented to be used by itself. Please use [**apistos**](https://crates.io/crates/apistos) instead.
4
5use crate::internal::schemas::Schemas;
6use crate::internal::utils::extract_deprecated_from_attr;
7use crate::internal::{gen_item_ast, gen_open_api_impl};
8use crate::openapi_cookie_attr::parse_openapi_cookie_attrs;
9use crate::openapi_error_attr::parse_openapi_error_attrs;
10use crate::openapi_header_attr::parse_openapi_header_attrs;
11use crate::openapi_security_attr::parse_openapi_security_attrs;
12use crate::operation_attr::parse_openapi_operation_attrs;
13use convert_case::{Case, Casing};
14use darling::ast::NestedMeta;
15use darling::Error;
16use proc_macro::TokenStream;
17use proc_macro2::Span;
18use proc_macro_error::{abort, proc_macro_error, OptionExt};
19use quote::{format_ident, quote};
20use syn::{DeriveInput, GenericParam, Ident, ItemFn};
21
22mod internal;
23mod openapi_cookie_attr;
24mod openapi_error_attr;
25mod openapi_header_attr;
26mod openapi_security_attr;
27mod operation_attr;
28
29const OPENAPI_STRUCT_PREFIX: &str = "__openapi_";
30
31/// Generates a custom OpenAPI type.
32///
33/// This `#[derive]` macro should be used in combination with [TypedSchema](trait.TypedSchema.html).
34///
35/// When deriving [ApiType], [ApiComponent] and [JsonSchema](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html) are automatically implemented and thus
36/// should not be derived.
37///
38/// ```rust
39/// use apistos::{ApiType, InstanceType, TypedSchema};
40///
41/// #[derive(Debug, Clone, ApiType)]
42/// pub struct Name(String);
43///
44/// impl TypedSchema for Name {
45///   fn schema_type() -> InstanceType {
46///     InstanceType::String
47///   }
48///
49///   fn format() -> Option<String> {
50///     None
51///   }
52/// }
53/// ```
54#[proc_macro_error]
55#[proc_macro_derive(ApiType)]
56pub fn derive_api_type(input: TokenStream) -> TokenStream {
57  let input = syn::parse_macro_input!(input as DeriveInput);
58  let DeriveInput {
59    attrs: _attrs,
60    ident,
61    data: _data,
62    generics,
63    vis: _vis,
64  } = input;
65
66  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
67  let component_name = quote!(#ident).to_string();
68  quote!(
69    #[automatically_derived]
70    impl #impl_generics schemars::JsonSchema for #ident #ty_generics #where_clause {
71       fn is_referenceable() -> bool {
72        false
73      }
74
75      fn schema_name() -> String {
76        #component_name.to_string()
77      }
78
79      fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> apistos::Schema {
80        let instance_type = <Self as TypedSchema>::schema_type();
81        apistos::Schema::Object(apistos::SchemaObject {
82          instance_type: Some(apistos::SingleOrVec::Single(Box::new(instance_type))),
83          format: <Self as TypedSchema>::format(),
84          ..Default::default()
85        })
86      }
87    }
88
89    #[automatically_derived]
90    impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
91      fn child_schemas() -> Vec<(String, apistos::reference_or::ReferenceOr<apistos::Schema>)> {
92        vec![]
93      }
94
95      fn schema() -> Option<(String, apistos::reference_or::ReferenceOr<apistos::Schema>)> {
96        Some((
97          #component_name.to_string(),
98          apistos::reference_or::ReferenceOr::Object(apistos::Schema::Object(apistos::SchemaObject {
99            instance_type: Some(apistos::SingleOrVec::Single(Box::new(<#ident #ty_generics>::schema_type()))),
100            format: <#ident #ty_generics>::format(),
101            ..Default::default()
102          }))
103        ))
104      }
105    }
106  )
107  .into()
108}
109
110/// Generates a reusable OpenAPI schema.
111///
112/// This `#[derive]` macro should be used in combination with [api_operation](attr.api_operation.html).
113///
114/// This macro requires your type to derive [JsonSchema](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html).
115///
116/// ```rust
117/// use apistos::ApiComponent;
118/// use schemars::JsonSchema;
119/// use garde::Validate;
120///
121/// #[derive(Debug, Clone, JsonSchema, ApiComponent, Validate)]
122/// pub(crate) struct QueryTag {
123///   #[garde(length(min = 2))]
124///   #[schemars(length(min = 2))]
125///   pub(crate) tags: Vec<String>,
126/// }
127/// ```
128///
129/// Because this macro requires [JsonSchema](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html), all attributes supported by [JsonSchema](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html) are forwarded to
130/// this implementation.
131#[proc_macro_error]
132#[proc_macro_derive(ApiComponent)]
133pub fn derive_api_component(input: TokenStream) -> TokenStream {
134  let input = syn::parse_macro_input!(input as DeriveInput);
135  let DeriveInput {
136    attrs: _attrs,
137    ident,
138    data: _data,
139    generics,
140    vis: _vis,
141  } = input;
142
143  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
144  let schema_impl = Schemas { deprecated: false };
145  quote!(
146    #[automatically_derived]
147    impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
148      #schema_impl
149    }
150  )
151  .into()
152}
153
154/// Generates a reusable OpenAPI security scheme.
155///
156/// This `#[derive]` macro should be used in combination with [api_operation](attr.api_operation.html).
157/// The macro requires one and only one `openapi_security`.
158///
159/// ```rust
160/// use apistos::ApiSecurity;
161///
162/// #[derive(ApiSecurity)]
163/// #[openapi_security(scheme(security_type(api_key(name = "api_key", api_key_in = "header"))))]
164/// pub struct ApiKey;
165/// ```
166///
167/// # `#[openapi_security(...)]` options:
168/// - `name = "..."` an optional name for your security definition. If not provided, the struct ident will be used.
169/// - `scheme(...)` a **required** parameter with:
170///   - `description = "..."` an optional description
171///   - `security_type(...)` a **required** parameter with one of
172///     - `oauth2(flows(...))` with
173///       - `implicit(...)` with `authorization_url = "..."` a **required** parameter, `refresh_url = "..."` an optional parameter and `scopes(scope = "...", description = "...")` a list of scopes
174///       - `password(...)` with `token_url = "..."` a **required** parameter, `refresh_url = "..."` an optional parameter and `scopes(scope = "...", description = "...")` a list of scopes
175///       - `client_credentials(...)` with `token_url = "..."` a **required** parameter, `refresh_url = "..."` an optional parameter and `scopes(scope = "...", description = "...")` a list of scopes
176///       - `authorization_code(...)` with `token_url = "..."` a **required** parameter, `refresh_url = "..."` an optional parameter and `scopes(scope = "...", description = "...")` a list of scopes
177///     - `api_key(...)` with
178///       - `name = "..."` a **required** parameter
179///       - `api_key_in = "..."` a **required** parameter being one of `query`, `header` or `cookie`
180///     - `http(...)` with
181///       - `scheme = "..."` a **required** parameter
182///       - `bearer_format = "..."` a **required** parameter
183///     - `open_id_connect(open_id_connect_url = "...")`
184///
185/// # Examples:
186///
187/// ## **oauth2**
188/// ```rust
189/// use apistos::ApiSecurity;
190///
191/// #[derive(ApiSecurity)]
192/// #[openapi_security(scheme(security_type(oauth2(flows(implicit(
193///   authorization_url = "https://authorize.com",
194///   refresh_url = "https://refresh.com",
195///   scopes(scope = "all:read", description = "Read all the things"),
196///   scopes(scope = "all:write", description = "Write all the things")
197/// ))))))]
198/// pub struct ApiKey;
199/// ```
200///
201/// ## **api_key**
202/// ```rust
203/// use apistos::ApiSecurity;
204///
205/// #[derive(ApiSecurity)]
206/// #[openapi_security(scheme(security_type(api_key(name = "api_key", api_key_in = "header"))))]
207/// pub struct ApiKey;
208/// ```
209///
210/// ## **http**
211/// ```rust
212/// use apistos::ApiSecurity;
213///
214/// #[derive(ApiSecurity)]
215/// #[openapi_security(scheme(security_type(http(scheme = "bearer", bearer_format = "JWT"))))]
216/// pub struct ApiKey;
217/// ```
218///
219/// ## **open_id_connect**
220/// ```rust
221/// use apistos::ApiSecurity;
222///
223/// #[derive(ApiSecurity)]
224/// #[openapi_security(scheme(security_type(open_id_connect(open_id_connect_url = "https://connect.com"))))]
225/// pub struct ApiKey;
226/// ```
227#[proc_macro_error]
228#[proc_macro_derive(ApiSecurity, attributes(openapi_security))]
229pub fn derive_api_security(input: TokenStream) -> TokenStream {
230  let input = syn::parse_macro_input!(input as DeriveInput);
231  let DeriveInput {
232    attrs,
233    ident,
234    data: _data,
235    generics,
236    vis: _vis,
237  } = input;
238
239  let security_name: String = ident.to_string().to_case(Case::Snake);
240  let openapi_security_attributes = parse_openapi_security_attrs(&attrs, security_name).expect_or_abort(
241    "expected #[openapi_security(...)] attribute to be present when used with ApiSecurity derive trait",
242  );
243  let security_name = &openapi_security_attributes.name;
244
245  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
246  quote!(
247    #[automatically_derived]
248    impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
249      fn child_schemas() -> Vec<(String, apistos::reference_or::ReferenceOr<apistos::Schema>)> {
250        vec![]
251      }
252
253      fn schema() -> Option<(String, apistos::reference_or::ReferenceOr<apistos::Schema>)> {
254        None
255      }
256
257      fn securities() -> std::collections::BTreeMap<String, apistos::security::SecurityScheme> {
258        #openapi_security_attributes
259      }
260
261      fn security_requirement_name() -> Option<String> {
262        Some(#security_name.to_string())
263      }
264    }
265  )
266  .into()
267}
268
269/// Generates a reusable OpenAPI header schema.
270///
271/// This `#[derive]` macro should be used in combination with [api_operation](attr.api_operation.html).
272/// The macro requires one and only one `openapi_header`.
273///
274/// This macro requires your type to derive [JsonSchema](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html).
275///
276/// ```rust
277/// use apistos::ApiHeader;
278/// use schemars::JsonSchema;
279///
280/// #[derive(Debug, Clone, JsonSchema, ApiHeader)]
281/// #[openapi_header(
282///   name = "X-Organization-Slug",
283///   description = "Organization of the current caller",
284///   required = true
285/// )]
286/// pub struct OrganizationSlug(String);
287/// ```
288///
289/// # `#[openapi_header(...)]` options:
290/// - `name = "..."` a **required** parameter with the header name
291/// - `description = "..."` an optional description for the header
292/// - `required = false` an optional parameter, default value is false
293/// - `deprecated = false` an optional parameter, default value is false
294///
295/// Because this macro requires [JsonSchema](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html), all attributes supported by [JsonSchema](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html) are forwarded to
296/// this implementation.
297#[proc_macro_error]
298#[proc_macro_derive(ApiHeader, attributes(openapi_header))]
299pub fn derive_api_header(input: TokenStream) -> TokenStream {
300  let input = syn::parse_macro_input!(input as DeriveInput);
301  let DeriveInput {
302    attrs,
303    ident,
304    data: _data,
305    generics,
306    vis: _vis,
307  } = input;
308
309  let deprecated = extract_deprecated_from_attr(&attrs);
310
311  let openapi_header_attributes = parse_openapi_header_attrs(&attrs, deprecated)
312    .expect_or_abort("expected #[openapi_header(...)] attribute to be present when used with ApiHeader derive trait");
313
314  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
315  let schema_impl = Schemas {
316    deprecated: openapi_header_attributes.deprecated.unwrap_or_default(),
317  };
318  quote!(
319    #[automatically_derived]
320    impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
321      #schema_impl
322    }
323
324    #[automatically_derived]
325    impl #impl_generics apistos::ApiHeader for #ident #ty_generics #where_clause {
326      #openapi_header_attributes
327    }
328  )
329  .into()
330}
331
332/// Generates a reusable OpenAPI parameter schema in cookie.
333///
334/// This `#[derive]` macro should be used in combination with [api_operation](attr.api_operation.html).
335/// The macro requires one and only one `openapi_cookie`.
336///
337/// This macro requires your type to derive [JsonSchema](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html).
338///
339/// ```rust
340/// use apistos::ApiCookie;
341/// use schemars::JsonSchema;
342///
343/// #[derive(Debug, Clone, JsonSchema, ApiCookie)]
344/// #[openapi_cookie(
345///   name = "X-Organization-Slug",
346///   description = "Organization of the current caller",
347///   required = true
348/// )]
349/// pub struct OrganizationSlugCookie(String);
350/// ```
351///
352/// # `#[openapi_cookie(...)]` options:
353/// - `name = "..."` a **required** parameter with the header name
354/// - `description = "..."` an optional description for the header
355/// - `required = false` an optional parameter, default value is false
356/// - `deprecated = false` an optional parameter, default value is false
357///
358/// Because this macro requires [JsonSchema](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html), all attributes supported by [JsonSchema](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html) are forwarded to
359/// this implementation.
360#[proc_macro_error]
361#[proc_macro_derive(ApiCookie, attributes(openapi_cookie))]
362pub fn derive_api_cookie(input: TokenStream) -> TokenStream {
363  let input = syn::parse_macro_input!(input as DeriveInput);
364  let DeriveInput {
365    attrs,
366    ident,
367    data: _data,
368    generics,
369    vis: _vis,
370  } = input;
371
372  let deprecated = extract_deprecated_from_attr(&attrs);
373
374  let openapi_cookie_attributes = parse_openapi_cookie_attrs(&attrs, deprecated)
375    .expect_or_abort("expected #[openapi_cookie(...)] attribute to be present when used with ApiCookie derive trait");
376
377  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
378  quote!(
379    #[automatically_derived]
380    impl #impl_generics apistos::ApiComponent for #ident #ty_generics #where_clause {
381      #openapi_cookie_attributes
382    }
383  )
384  .into()
385}
386
387/// Generates a reusable OpenAPI error schema.
388///
389/// This `#[derive]` macro should be used in combination with [api_operation](attr.api_operation.html).
390/// The macro only supports one `openapi_error`.
391///
392/// ```rust
393/// use apistos::ApiErrorComponent;
394///
395/// #[derive(Clone, ApiErrorComponent)]
396/// #[openapi_error(
397///   status(code = 403),
398///   status(code = 404),
399///   status(code = 405, description = "Invalid input"),
400///   status(code = 409)
401/// )]
402/// pub enum ErrorResponse {
403///   MethodNotAllowed(String),
404///   NotFound(String),
405///   Conflict(String),
406///   Unauthorized(String),
407/// }
408/// ```
409///
410/// # `#[openapi_error(...)]` options:
411/// - `status(...)` a list of possible error status with
412///   - `code = 000` a **required** http status code
413///   - `description = "..."` an optional description, default is the canonical reason of the given status code
414#[proc_macro_error]
415#[proc_macro_derive(ApiErrorComponent, attributes(openapi_error))]
416pub fn derive_api_error(input: TokenStream) -> TokenStream {
417  let input = syn::parse_macro_input!(input as DeriveInput);
418  let DeriveInput {
419    attrs,
420    ident,
421    data: _data,
422    generics,
423    vis: _vis,
424  } = input;
425
426  let openapi_error_attributes = parse_openapi_error_attrs(&attrs).expect_or_abort(
427    "expected #[openapi_error(...)] attribute to be present when used with ApiErrorComponent derive trait",
428  );
429
430  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
431  quote!(
432    #[automatically_derived]
433    impl #impl_generics apistos::ApiErrorComponent for #ident #ty_generics #where_clause {
434      #openapi_error_attributes
435    }
436  )
437  .into()
438}
439
440/// Operation attribute macro implementing [PathItemDefinition](path_item_definition/trait.PathItemDefinition.html) for the decorated handler function.
441///
442/// ```rust
443/// use std::fmt::Display;
444/// use actix_web::web::Json;
445/// use actix_web::http::StatusCode;
446/// use actix_web::ResponseError;
447/// use core::fmt::Formatter;
448/// use apistos::actix::CreatedJson;
449/// use apistos::{api_operation, ApiComponent, ApiErrorComponent};
450/// use schemars::JsonSchema;
451/// use serde::{Serialize, Deserialize};
452///
453/// #[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, ApiComponent)]
454/// pub struct Test {
455///   pub test: String
456/// }
457///
458/// #[derive(Serialize, Deserialize, Debug, Clone, ApiErrorComponent)]
459/// #[openapi_error(
460///   status(code = 405, description = "Invalid input"),
461/// )]
462/// pub enum ErrorResponse {
463///   MethodNotAllowed(String),
464/// }
465///
466/// impl Display for ErrorResponse {
467///   fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
468///     todo!()
469///   }
470/// }
471///
472/// impl ResponseError for ErrorResponse {
473///   fn status_code(&self) -> StatusCode {
474///     todo!()
475///   }
476/// }
477///
478/// #[api_operation(
479///   tag = "pet",
480///   summary = "Add a new pet to the store",
481///   description = r###"Add a new pet to the store
482///     Plop"###,
483///   error_code = 405
484/// )]
485/// pub(crate) async fn test(
486///   body: Json<Test>,
487/// ) -> Result<CreatedJson<Test>, ErrorResponse> {
488///   Ok(CreatedJson(body.0))
489/// }
490/// ```
491///
492/// # `#[api_operation(...)]` options:
493///   - `skip` a bool allowing to skip documentation for the decorated handler. No component
494///  strictly associated to this operation will be document in the resulting openapi definition.
495///   - `skip_args = "..."` an optional list of arguments to skip. `Apistos` will not try to generate the
496///  documentation for those args which prevent errors linked to missing `ApiComponent` implementation.
497///   - `deprecated` a bool indicating the operation is deprecated. Deprecation can also be declared
498///  with rust `#[deprecated]` decorator.
499///   - `operation_id = "..."` an optional operation id for this operation. Default is the handler's fn name.
500///   - `summary = "..."` an optional summary
501///   - `description = "..."` an optional description
502///   - `tag = "..."` an optional list of tags associated with this operation (define tag multiple times to add to the list)
503///   - `security_scope(...)` an optional list representing which security scopes apply for a given operation with
504///       - `name = "..."` a mandatory name referencing one of the security definitions
505///       - `scope(...)` a list of scopes applying to this operation
506///   - `error_code = 00` an optional list of error codes to document only theses
507///   - `consumes = "..."` allow to override body content type
508///   - `produces = "..."` allow to override response content type
509///
510/// If `summary` or `description` are not provided, a default value will be extracted from the comments. The first line will be used as summary while the rest will be part of the description.
511///
512/// For example:
513/// ```rust
514/// use actix_web::web::Json;
515/// use std::fmt::Display;
516/// use actix_web::http::StatusCode;
517/// use actix_web::ResponseError;
518/// use core::fmt::Formatter;
519/// use apistos::actix::CreatedJson;
520/// use apistos::{api_operation, ApiComponent, ApiErrorComponent};
521/// use schemars::JsonSchema;
522/// use serde::{Serialize, Deserialize};
523///
524/// #[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, ApiComponent)]
525/// pub struct Test {
526///   pub test: String
527/// }
528///
529/// #[derive(Serialize, Deserialize, Debug, Clone, ApiErrorComponent)]
530/// #[openapi_error(
531///   status(code = 405, description = "Invalid input"),
532/// )]
533/// pub enum ErrorResponse {
534///   MethodNotAllowed(String),
535/// }
536///
537/// impl Display for ErrorResponse {
538///   fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
539///     todo!()
540///   }
541/// }
542///
543/// impl ResponseError for ErrorResponse {
544///   fn status_code(&self) -> StatusCode {
545///     todo!()
546///   }
547/// }
548///
549/// #[api_operation(
550///   tag = "pet",
551///   summary = "Add a new pet to the store",
552///   description = r###"Add a new pet to the store
553///     Plop"###,
554/// )]
555/// pub(crate) async fn test(
556///   body: Json<Test>,
557/// ) -> Result<CreatedJson<Test>, ErrorResponse> {
558///   Ok(CreatedJson(body.0))
559/// }
560/// ```
561///
562/// is equivalent to
563/// ```rust
564/// use std::fmt::Display;
565/// use actix_web::web::Json;
566/// use actix_web::http::StatusCode;
567/// use actix_web::ResponseError;
568/// use core::fmt::Formatter;
569/// use apistos::actix::CreatedJson;
570/// use apistos::{api_operation, ApiComponent, ApiErrorComponent};
571/// use schemars::JsonSchema;
572/// use serde::{Serialize, Deserialize};
573///
574/// #[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, ApiComponent)]
575/// pub struct Test {
576///   pub test: String
577/// }
578///
579/// #[derive(Serialize, Deserialize, Debug, Clone, ApiErrorComponent)]
580/// #[openapi_error(
581///   status(code = 405, description = "Invalid input"),
582/// )]
583/// pub enum ErrorResponse {
584///   MethodNotAllowed(String),
585/// }
586///
587/// impl Display for ErrorResponse {
588///   fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
589///     todo!()
590///   }
591/// }
592///
593/// impl ResponseError for ErrorResponse {
594///   fn status_code(&self) -> StatusCode {
595///     todo!()
596///   }
597/// }
598///
599/// /// Add a new pet to the store
600/// /// Add a new pet to the store
601/// /// Plop
602/// #[api_operation(
603///   tag = "pet",
604/// )]
605/// pub(crate) async fn test(
606///   body: Json<Test>,
607/// ) -> Result<CreatedJson<Test>, ErrorResponse> {
608///   Ok(CreatedJson(body.0))
609/// }
610/// ```
611#[proc_macro_error]
612#[proc_macro_attribute]
613pub fn api_operation(attr: TokenStream, item: TokenStream) -> TokenStream {
614  let attr_args = match NestedMeta::parse_meta_list(attr.into()) {
615    Ok(v) => v,
616    Err(e) => {
617      return TokenStream::from(Error::from(e).write_errors());
618    }
619  };
620
621  let operation_attribute = parse_openapi_operation_attrs(&attr_args);
622
623  let default_span = Span::call_site();
624  let item_ast = match syn::parse::<ItemFn>(item) {
625    Ok(v) => v,
626    Err(e) => abort!(e.span(), format!("{e}")),
627  };
628
629  let s_name = format!("{OPENAPI_STRUCT_PREFIX}{}", item_ast.sig.ident);
630  let openapi_struct = Ident::new(&s_name, default_span);
631
632  let generics = &item_ast.sig.generics.clone();
633  let mut generics_call = quote!();
634  let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
635  let openapi_struct_def = if !generics.params.is_empty() {
636    let mut generic_types_idents = vec![];
637    for param in &generics.params {
638      match param {
639        GenericParam::Lifetime(_) => {}
640        GenericParam::Const(_) => {}
641        GenericParam::Type(_type) => generic_types_idents.push(_type.ident.clone()),
642      }
643    }
644    let turbofish = ty_generics.as_turbofish();
645    let mut phantom_params = quote!();
646    let mut phantom_params_names = quote!();
647    for generic_types_ident in generic_types_idents {
648      let param_name = Ident::new(
649        &format_ident!("p_{}", generic_types_ident).to_string().to_lowercase(),
650        Span::call_site(),
651      );
652      phantom_params_names.extend(quote!(#param_name: std::marker::PhantomData,));
653      phantom_params.extend(quote!(#param_name: std::marker::PhantomData < #generic_types_ident >,))
654    }
655    generics_call = quote!(#turbofish { #phantom_params_names });
656
657    quote!(struct #openapi_struct #impl_generics #where_clause { #phantom_params })
658  } else {
659    quote!(struct #openapi_struct;)
660  };
661
662  let (responder_wrapper, generated_item_ast) =
663    gen_item_ast(default_span, item_ast, &openapi_struct, &ty_generics, &generics_call);
664  let generated_item_fn = match syn::parse::<ItemFn>(generated_item_ast.clone().into()) {
665    Ok(v) => v,
666    Err(e) => abort!(e.span(), format!("{e}")),
667  };
668  let open_api_def = gen_open_api_impl(
669    &generated_item_fn,
670    operation_attribute,
671    &openapi_struct,
672    &openapi_struct_def,
673    &impl_generics,
674    &ty_generics,
675    where_clause,
676    &responder_wrapper,
677  );
678
679  quote!(
680    #open_api_def
681
682    #generated_item_ast
683  )
684  .into()
685}
686
687// Imports bellow aim at making clippy happy. Those dependencies are necessary for doc-test.
688#[cfg(test)]
689use apistos as _;
690#[cfg(test)]
691use garde as _;
692#[cfg(test)]
693use schemars as _;
694#[cfg(test)]
695use serde as _;