trillium_error/
lib.rs

1/*!
2This crate adds support for error handling in [trillium](https://trillium.rs) web framework.
3
4Due to limitations in Rust, error handling is currently not supported in trillium. When the language
5adds capability to express bounds for `for<'a> Fn(&'a Conn) -> impl Future<Output=…> + 'a`, trillium
6will add first class support for error handling. Until then `trillium-error` provides
7a proc macro to help write handlers with error. For more details please refer to the discussion
8[here](https://github.com/trillium-rs/trillium/discussions/31).
9
10```ignore
11use trillium_error::handler;
12
13#[derive(thiserror::Error, Debug)]
14pub enum AppError {
15    #[error("Custom error")]
16    CustomError,
17    #[error("IO error")]
18    IoError(std::io::Error),
19}
20
21impl From<std::io::Error> for AppError {
22    fn from(err: std::io::Error) -> Self {
23        AppError::IoError(err)
24    }
25}
26
27#[async_trait]
28impl Handler for AppError {
29    async fn run(&self, conn: Conn) -> Conn {
30        conn.with_status(500).with_body("Internal Server Error")
31    }
32}
33
34#[handler]
35async fn helloworld(conn: &mut Conn) -> Result<(), AppError> {
36    conn.set_status(200);
37    conn.set_body("hello world");
38    // Ok(())
39    Err(AppError::CustomError)
40}
41
42fn main() {
43    trillium_tokio::run(helloworld);
44}
45```
46*/
47use proc_macro::TokenStream;
48use quote::quote;
49use syn::{parse_macro_input, Item};
50
51#[proc_macro_attribute]
52pub fn handler(_args: TokenStream, input: TokenStream) -> TokenStream {
53    let item = parse_macro_input!(input as Item);
54
55    let output: syn::Result<TokenStream> = match item {
56        Item::Fn(mut item_fn) => {
57            let attrs = &item_fn.attrs;
58            let vis = &item_fn.vis;
59            let sig = &mut item_fn.sig;
60            let name = &sig.ident;
61            let body = &item_fn.block;
62            let docs = item_fn
63                .attrs
64                .iter()
65                .filter(|attr| attr.path.is_ident("doc"))
66                .cloned()
67                .collect::<Vec<_>>();
68
69            Ok(quote! {
70                #(#docs)*
71                #[allow(non_camel_case_types)]
72                #[derive(Debug)]
73                #vis struct #name;
74
75                impl #name {
76                    #(#attrs)*
77                    #sig {
78                        #body
79                    }
80                }
81
82                #[trillium::async_trait]
83                impl trillium::Handler for #name {
84                    async fn run(&self, mut conn: trillium::Conn) -> trillium::Conn {
85                        match Self::#name(&mut conn).await {
86                            Ok(_) => conn,
87                            Err(e) => e.run(conn).await
88                        }
89                    }
90                }
91            }
92            .into())
93        }
94        _ => Err(syn::Error::new_spanned(
95            item,
96            "#[handler] must added to `impl` or `fn`",
97        )),
98    };
99
100    match output {
101        Ok(stream) => stream.into(),
102        Err(e) => e.to_compile_error().into(),
103    }
104}