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}