aide_macros/
lib.rs

1use darling::FromDeriveInput;
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{parse_macro_input, parse_quote, DeriveInput, Type};
5
6extern crate proc_macro;
7
8#[derive(Default, FromDeriveInput)]
9#[darling(default, attributes(aide))]
10struct OperationIoOpts {
11    input: bool,
12    input_with: Option<Type>,
13    output: bool,
14    output_with: Option<Type>,
15    json_schema: bool,
16}
17
18/// A helper to reduce boilerplate for implementing [`OperationInput`]
19/// and [`OperationOutput`] for common use-cases.
20///
21/// # Examples
22///
23/// The following implements an empty [`OperationInput`] and
24/// [`OperationOutput`] so that the type can be used in documented
25/// handlers but does not modify the documentation generation in any way.
26///
27/// ```ignore
28/// use aide::{OperationInput, OperationOutput};
29/// # use aide_macros::{OperationInput, OperationOutput};
30///
31/// #[derive(OperationIo)]
32/// struct MyExtractor;
33/// ```
34///
35/// By default both [`OperationInput`] and [`OperationOutput`] are implemented.
36/// It is possible to restrict either with the `input` and `output` parameters.
37///
38/// The following will only implement [`OperationOutput`]:
39///
40/// ```ignore
41/// #[derive(OperationIo)]
42/// #[aide(output)]
43/// struct MyExtractor;
44/// ```
45///
46/// We can use the implementations of another type,
47/// this is useful for wrapping other (e.g. `Json`) extractors
48/// that might alter runtime behaviour but the documentation remains the same.
49///
50/// Additionally passing the `json_schema` flag will put a
51/// [`JsonSchema`] bound to all generic parameters.
52///
53/// ```ignore
54/// #[derive(OperationIo)]
55/// #[aide(
56///     input_with = "some_other::Json<T>",
57///     output_with = "some_other::Json<T>",
58///     json_schema
59/// )]
60/// struct Json<T>(pub T);
61/// ```
62///
63/// [`JsonSchema`]: https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html
64/// [`OperationInput`]: https://docs.rs/aide/latest/aide/operation/trait.OperationInput.html
65/// [`OperationOutput`]: https://docs.rs/aide/latest/aide/operation/trait.OperationOutput.html
66#[proc_macro_derive(OperationIo, attributes(aide))]
67pub fn derive_operation_io(ts: TokenStream) -> TokenStream {
68    let mut derive_input = parse_macro_input!(ts as DeriveInput);
69
70    let OperationIoOpts {
71        input_with,
72        output_with,
73        input,
74        output,
75        json_schema,
76    } = OperationIoOpts::from_derive_input(&derive_input).unwrap();
77
78    let name = &derive_input.ident;
79
80    let generic_params = derive_input
81        .generics
82        .params
83        .iter()
84        .filter_map(|p| match p {
85            syn::GenericParam::Type(t) => Some(t.ident.clone()),
86            _ => None,
87        })
88        .collect::<Vec<_>>();
89
90    if json_schema {
91        let wh = derive_input.generics.make_where_clause();
92
93        for param in generic_params {
94            wh.predicates
95                .push(parse_quote!(#param: schemars::JsonSchema));
96        }
97    }
98
99    let (i_gen, t_gen, w_gen) = derive_input.generics.split_for_impl();
100
101    let mut ts = quote!();
102
103    if !input && !output && input_with.is_none() && output_with.is_none() {
104        ts.extend(quote! {
105            impl #i_gen aide::OperationInput for #name #t_gen #w_gen {}
106            impl #i_gen aide::OperationOutput for #name #t_gen #w_gen {
107                type Inner = Self;
108            }
109        });
110    } else {
111        if input {
112            ts.extend(quote! {
113                impl #i_gen aide::OperationInput for #name #t_gen #w_gen {}
114            });
115        }
116        if output {
117            ts.extend(quote! {
118                impl #i_gen aide::OperationOutput for #name #t_gen #w_gen {
119                    type Inner = Self;
120                }
121            });
122        }
123
124        if let Some(input) = input_with {
125            ts.extend(quote! {
126                impl #i_gen aide::OperationInput for #name #t_gen #w_gen {
127                    fn operation_input(
128                        ctx: &mut aide::generate::GenContext,
129                        operation: &mut aide::openapi::Operation
130                    ) {
131                        <#input as aide::OperationInput>::operation_input(
132                            ctx,
133                            operation
134                        );
135                    }
136                }
137            });
138        }
139
140        if let Some(output) = output_with {
141            ts.extend(quote! {
142                impl #i_gen aide::OperationOutput for #name #t_gen #w_gen {
143                    type Inner = <#output as aide::OperationOutput>::Inner;
144                    fn operation_response(
145                        ctx: &mut aide::generate::GenContext,
146                        operation: &mut aide::openapi::Operation
147                    ) -> Option<aide::openapi::Response> {
148                        <#output as aide::OperationOutput>::operation_response(
149                            ctx,
150                            operation
151                        )
152                    }
153                    fn inferred_responses(
154                        ctx: &mut aide::generate::GenContext,
155                        operation: &mut aide::openapi::Operation
156                    ) -> Vec<(Option<u16>, aide::openapi::Response)> {
157                        <#output as aide::OperationOutput>::inferred_responses(
158                            ctx,
159                            operation
160                        )
161                    }
162                }
163            });
164        }
165    }
166
167    ts.into()
168}