salvo_macros/
lib.rs

1//! The macros lib of Salvo web framework.
2//!
3//! Read more: <https://salvo.rs>
4#![doc(html_favicon_url = "https://salvo.rs/favicon-32x32.png")]
5#![doc(html_logo_url = "https://salvo.rs/images/logo.svg")]
6#![cfg_attr(docsrs, feature(doc_cfg))]
7
8use proc_macro::TokenStream;
9use syn::{DeriveInput, Item, parse_macro_input};
10
11mod attribute;
12mod extract;
13mod handler;
14mod shared;
15
16pub(crate) use salvo_serde_util as serde_util;
17use shared::*;
18
19/// `handler` is a macro to help create `Handler` from function or impl block easily.
20///
21/// `Handler` is a trait, if `#[handler]` applied to `fn`,  `fn` will converted to a struct, and then implement `Handler`,
22/// after use `handler`, you don't need to care arguments' order, omit unused arguments.
23///
24/// View `salvo_core::handler` for more details.
25#[proc_macro_attribute]
26pub fn handler(_args: TokenStream, input: TokenStream) -> TokenStream {
27    let item = parse_macro_input!(input as Item);
28    match handler::generate(item) {
29        Ok(stream) => stream.into(),
30        Err(e) => e.to_compile_error().into(),
31    }
32}
33
34/// Generate code for extractible type.
35#[proc_macro_derive(Extractible, attributes(salvo))]
36pub fn derive_extractible(input: TokenStream) -> TokenStream {
37    let args = parse_macro_input!(input as DeriveInput);
38    match extract::generate(args) {
39        Ok(stream) => stream.into(),
40        Err(e) => e.to_compile_error().into(),
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use quote::quote;
47    use syn::parse2;
48
49    use super::*;
50
51    #[test]
52    fn test_handler_for_fn() {
53        let input = quote! {
54            #[handler]
55            async fn hello(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
56                res.render_plain_text("Hello World");
57            }
58        };
59        let item = parse2(input).unwrap();
60        assert_eq!(
61            handler::generate(item).unwrap().to_string(),
62            quote! {
63                #[allow(non_camel_case_types)]
64                #[derive(Debug)]
65                struct hello;
66                impl hello {
67                    async fn hello(req: &mut Request,depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
68                        {
69                            res.render_plain_text("Hello World");
70                        }
71                    }
72                }
73                #[salvo::async_trait]
74                impl salvo::Handler for hello {
75                    async fn handle(
76                        &self,
77                        __macro_gen_req: &mut salvo::Request,
78                        __macro_gen_depot: &mut salvo::Depot,
79                        __macro_gen_res: &mut salvo::Response,
80                        __macro_gen_ctrl: &mut salvo::FlowCtrl
81                    ) {
82                        Self::hello(__macro_gen_req, __macro_gen_depot, __macro_gen_res, __macro_gen_ctrl).await
83                    }
84                }
85            }
86            .to_string()
87        );
88    }
89
90    #[test]
91    fn test_handler_for_fn_return_result() {
92        let input = quote! {
93            #[handler]
94            async fn hello(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) -> Result<(), Error> {
95                Ok(())
96            }
97        };
98        let item = parse2(input).unwrap();
99        assert_eq!(
100            handler::generate(item).unwrap().to_string(),
101            quote!{
102                #[allow(non_camel_case_types)]
103                #[derive(Debug)]
104                struct hello;
105                impl hello {
106                    async fn hello(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl
107                    ) -> Result<(), Error> {
108                        {Ok(())}
109                    }
110                }
111                #[salvo::async_trait]
112                impl salvo::Handler for hello {
113                    async fn handle(
114                        &self,
115                        __macro_gen_req: &mut salvo::Request,
116                        __macro_gen_depot: &mut salvo::Depot,
117                        __macro_gen_res: &mut salvo::Response,
118                        __macro_gen_ctrl: &mut salvo::FlowCtrl
119                    ) {
120                        salvo::Writer::write(Self::hello(__macro_gen_req, __macro_gen_depot, __macro_gen_res, __macro_gen_ctrl).await, __macro_gen_req, __macro_gen_depot, __macro_gen_res).await;
121                    }
122                }
123            }
124            .to_string()
125        );
126    }
127
128    #[test]
129    fn test_handler_for_impl() {
130        let input = quote! {
131            #[handler]
132            impl Hello {
133                fn handle(req: &mut Request, depot: &mut Depot, res: &mut Response) {
134                    res.render_plain_text("Hello World");
135                }
136            }
137        };
138        let item = parse2(input).unwrap();
139        assert_eq!(
140            handler::generate(item).unwrap().to_string(),
141            quote! {
142                #[handler]
143                impl Hello {
144                    fn handle(req: &mut Request, depot: &mut Depot, res: &mut Response) {
145                        res.render_plain_text("Hello World");
146                    }
147                }
148                #[salvo::async_trait]
149                impl salvo::Handler for Hello {
150                    async fn handle(
151                        &self,
152                        __macro_gen_req: &mut salvo::Request,
153                        __macro_gen_depot: &mut salvo::Depot,
154                        __macro_gen_res: &mut salvo::Response,
155                        __macro_gen_ctrl: &mut salvo::FlowCtrl
156                    ) {
157                        Self::handle(__macro_gen_req, __macro_gen_depot, __macro_gen_res)
158                    }
159                }
160            }
161            .to_string()
162        );
163    }
164
165    #[test]
166    fn test_extract_simple() {
167        let input = quote! {
168            #[salvo(extract(default_source(from = "body")))]
169            struct BadMan<'a> {
170                #[salvo(extract(source(from = "query")))]
171                id: i64,
172                username: String,
173            }
174        };
175        let item = parse2(input).unwrap();
176        assert_eq!(
177            extract::generate(item).unwrap().to_string(),
178            quote!{
179                impl<'__macro_gen_ex: 'a, 'a> salvo::extract::Extractible<'__macro_gen_ex> for BadMan<'a> {
180                    fn metadata() -> &'static salvo::extract::Metadata {
181                        static METADATA: ::std::sync::OnceLock<salvo::extract::Metadata> = ::std::sync::OnceLock::new();
182                        METADATA.get_or_init(|| {
183                            let mut metadata = salvo::extract::Metadata::new("BadMan");
184                            metadata = metadata.add_default_source(salvo::extract::metadata::Source::new(
185                                salvo::extract::metadata::SourceFrom::Body,
186                                salvo::extract::metadata::SourceParser::Smart
187                            ));
188                            let mut field = salvo::extract::metadata::Field::new("id");
189                            field = field.add_source(salvo::extract::metadata::Source::new(
190                                salvo::extract::metadata::SourceFrom::Query,
191                                salvo::extract::metadata::SourceParser::Smart
192                            ));
193                            metadata = metadata.add_field(field);
194                            let mut field = salvo::extract::metadata::Field::new("username");
195                            metadata = metadata.add_field(field);
196                            metadata
197                        })
198                    }
199                    #[allow(refining_impl_trait)]
200                    async fn extract(req: &'__macro_gen_ex mut salvo::http::Request) -> Result<Self, salvo::http::ParseError>
201                    where
202                        Self: Sized {
203                        salvo::serde::from_request(req, Self::metadata()).await
204                    }
205                }
206            }
207            .to_string()
208        );
209    }
210
211    #[test]
212    fn test_extract_with_lifetime() {
213        let input = quote! {
214            #[salvo(extract(
215                default_source(from = "query"),
216                default_source(from = "param"),
217                default_source(from = "body")
218            ))]
219            struct BadMan<'a> {
220                id: i64,
221                username: String,
222                first_name: &'a str,
223                last_name: String,
224                lovers: Vec<String>,
225            }
226        };
227        let item = parse2(input).unwrap();
228        assert_eq!(
229            extract::generate(item).unwrap().to_string(),
230            quote!{
231                impl<'__macro_gen_ex: 'a, 'a> salvo::extract::Extractible<'__macro_gen_ex> for BadMan<'a> {
232                    fn metadata() -> &'static salvo::extract::Metadata {
233                        static METADATA: ::std::sync::OnceLock<salvo::extract::Metadata> = ::std::sync::OnceLock::new();
234                        METADATA.get_or_init(|| {
235                            let mut metadata = salvo::extract::Metadata::new("BadMan");
236                            metadata = metadata.add_default_source(salvo::extract::metadata::Source::new(
237                                salvo::extract::metadata::SourceFrom::Query,
238                                salvo::extract::metadata::SourceParser::Smart
239                            ));
240                            metadata = metadata.add_default_source(salvo::extract::metadata::Source::new(
241                                salvo::extract::metadata::SourceFrom::Param,
242                                salvo::extract::metadata::SourceParser::Smart
243                            ));
244                            metadata = metadata.add_default_source(salvo::extract::metadata::Source::new(
245                                salvo::extract::metadata::SourceFrom::Body,
246                                salvo::extract::metadata::SourceParser::Smart
247                            ));
248                            let mut field = salvo::extract::metadata::Field::new("id");
249                            metadata = metadata.add_field(field);
250                            let mut field = salvo::extract::metadata::Field::new("username");
251                            metadata = metadata.add_field(field);
252                            let mut field = salvo::extract::metadata::Field::new("first_name");
253                            metadata = metadata.add_field(field);
254                            let mut field = salvo::extract::metadata::Field::new("last_name");
255                            metadata = metadata.add_field(field);
256                            let mut field = salvo::extract::metadata::Field::new("lovers");
257                            metadata = metadata.add_field(field);
258                            metadata
259                        })
260                    }
261                    #[allow(refining_impl_trait)]
262                    async fn extract(req: &'__macro_gen_ex mut salvo::http::Request) -> Result<Self, salvo::http::ParseError>
263                    where
264                        Self: Sized {
265                        salvo::serde::from_request(req, Self::metadata()).await
266                    }
267                }
268            }
269            .to_string()
270        );
271    }
272}