rocket_codegen/attribute/catch/
mod.rs

1mod parse;
2
3use devise::ext::SpanDiagnosticExt;
4use devise::{Spanned, Result};
5use proc_macro2::{TokenStream, Span};
6
7use crate::http_codegen::Optional;
8use crate::syn_ext::ReturnTypeExt;
9use crate::exports::*;
10
11pub fn _catch(
12    args: proc_macro::TokenStream,
13    input: proc_macro::TokenStream
14) -> Result<TokenStream> {
15    // Parse and validate all of the user's input.
16    let catch = parse::Attribute::parse(args.into(), input)?;
17
18    // Gather everything we'll need to generate the catcher.
19    let user_catcher_fn = &catch.function;
20    let user_catcher_fn_name = &catch.function.sig.ident;
21    let vis = &catch.function.vis;
22    let status_code = Optional(catch.status.map(|s| s.code));
23    let deprecated = catch.function.attrs.iter().find(|a| a.path().is_ident("deprecated"));
24
25    // Determine the number of parameters that will be passed in.
26    if catch.function.sig.inputs.len() > 2 {
27        return Err(catch.function.sig.paren_token.span.join()
28            .error("invalid number of arguments: must be zero, one, or two")
29            .help("catchers optionally take `&Request` or `Status, &Request`"));
30    }
31
32    // This ensures that "Responder not implemented" points to the return type.
33    let return_type_span = catch.function.sig.output.ty()
34        .map(|ty| ty.span())
35        .unwrap_or_else(Span::call_site);
36
37    // Set the `req` and `status` spans to that of their respective function
38    // arguments for a more correct `wrong type` error span. `rev` to be cute.
39    let codegen_args = &[__req, __status];
40    let inputs = catch.function.sig.inputs.iter().rev()
41        .zip(codegen_args.iter())
42        .map(|(fn_arg, codegen_arg)| match fn_arg {
43            syn::FnArg::Receiver(_) => codegen_arg.respanned(fn_arg.span()),
44            syn::FnArg::Typed(a) => codegen_arg.respanned(a.ty.span())
45        }).rev();
46
47    // We append `.await` to the function call if this is `async`.
48    let dot_await = catch.function.sig.asyncness
49        .map(|a| quote_spanned!(a.span() => .await));
50
51    let catcher_response = quote_spanned!(return_type_span => {
52        let ___responder = #user_catcher_fn_name(#(#inputs),*) #dot_await;
53        #_response::Responder::respond_to(___responder, #__req)?
54    });
55
56    // Generate the catcher, keeping the user's input around.
57    Ok(quote! {
58        #user_catcher_fn
59
60        #[doc(hidden)]
61        #[allow(nonstandard_style)]
62        /// Rocket code generated proxy structure.
63        #deprecated #vis struct #user_catcher_fn_name {  }
64
65        /// Rocket code generated proxy static conversion implementations.
66        #[allow(nonstandard_style, deprecated, clippy::style)]
67        impl #user_catcher_fn_name {
68            fn into_info(self) -> #_catcher::StaticInfo {
69                fn monomorphized_function<'__r>(
70                    #__status: #Status,
71                    #__req: &'__r #Request<'_>
72                ) -> #_catcher::BoxFuture<'__r> {
73                    #_Box::pin(async move {
74                        let __response = #catcher_response;
75                        #Response::build()
76                            .status(#__status)
77                            .merge(__response)
78                            .ok()
79                    })
80                }
81
82                #_catcher::StaticInfo {
83                    name: stringify!(#user_catcher_fn_name),
84                    code: #status_code,
85                    handler: monomorphized_function,
86                }
87            }
88
89            #[doc(hidden)]
90            pub fn into_catcher(self) -> #Catcher {
91                self.into_info().into()
92            }
93        }
94    })
95}
96
97pub fn catch_attribute(
98    args: proc_macro::TokenStream,
99    input: proc_macro::TokenStream
100) -> TokenStream {
101    _catch(args, input).unwrap_or_else(|d| d.emit_as_item_tokens())
102}