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