1#![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#[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#[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}