cgi_attributes/
lib.rs

1use proc_macro::TokenStream;
2use quote::{quote, quote_spanned};
3use syn::{spanned::Spanned, ReturnType, Type};
4
5fn looks_like_result(return_type: &ReturnType) -> bool {
6    if let ReturnType::Type(_, ty) = return_type {
7        if let Type::Path(p) = &**ty {
8            if let Some(seg) = p.path.segments.last() {
9                if seg.ident == "Result" {
10                    return true;
11                }
12            }
13        }
14    }
15
16    false
17}
18
19/// Enables a CGI main function.
20///
21/// # Examples
22///
23/// ```ignore
24/// #[cgi::main]
25/// fn main(request: cgi::Request) -> cgi::Response {
26///     todo!()
27/// }
28/// ```
29//#[cfg(not(test))] // NOTE: exporting main breaks tests, we should file an issue.
30#[proc_macro_attribute]
31pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
32    let input = syn::parse_macro_input!(item as syn::ItemFn);
33
34    let ret = &input.sig.output;
35    let inputs = &input.sig.inputs;
36    let name = &input.sig.ident;
37    let body = &input.block;
38    let attrs = &input.attrs;
39    let vis = &input.vis;
40
41    if name != "main" {
42        return TokenStream::from(quote_spanned! { name.span() =>
43            compile_error!("only the main function can be tagged with #[cgi::main]"),
44        });
45    }
46
47    if input.sig.asyncness.is_some() {
48        return TokenStream::from(quote_spanned! { input.span() =>
49            compile_error!("async not supported"),
50        });
51    }
52
53    let inner = if looks_like_result(ret) {
54        quote! {
55            cgi::handle(|request: cgi::Request| {
56                match inner_main(request) {
57                    Ok(resp) => resp,
58                    Err(err) => {
59                        eprintln!("{:?}", err);
60                        cgi::empty_response(500)
61                    }
62                }
63            })
64        }
65    } else {
66        quote! {
67            cgi::handle(inner_main)
68        }
69    };
70
71    let result = quote! {
72        #vis fn main() {
73            #(#attrs)*
74            fn inner_main(#inputs) #ret {
75                #body
76            }
77
78            #inner
79        }
80
81    };
82
83    result.into()
84}