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}