summer_boot_macro/
lib.rs

1//! 运行时宏处理
2//!
3//! # main
4//! 使用运行时宏来设置summerboot async运行时。参见[main]宏文档。
5//!
6//! # auto_scan
7//! 提供了基础的`auto_scan`功能用于发现并自动注册路由。
8//!
9//! # post、get、delete、put、patch、head、options、connect、trace
10//! 提供了简单的路由宏标注。
11//!
12//!
13
14use proc_macro::TokenStream;
15use proc_macro2::{Ident, Span};
16use quote::{quote, ToTokens};
17use serde::Deserialize;
18use serde_json::Value;
19use std::io::Read;
20use std::fs;
21use syn::{
22    parse_file, parse_macro_input, parse_quote, punctuated::Punctuated, AttributeArgs, Item,
23    ItemFn, Lit, Meta, NestedMeta, Pat, Stmt, Token,
24};
25
26/// 用于匹配项目根目录下的 `Cargo.toml` 文件。
27/// 匹配规则为:
28/// 1. workspace下的member的数组格式
29/// 2. 在package下的name字段
30#[derive(Debug, Deserialize)]
31struct ConfWorkSpace {
32    workspace: Option<Member>,
33    package: Option<Name>,
34}
35
36/// 匹配workspace下的member数组格式
37#[derive(Debug, Deserialize)]
38struct Member {
39    members: Option<Vec<String>>,
40}
41
42/// 匹配package下的name字段
43#[derive(Debug, Deserialize)]
44struct Name {
45    #[allow(dead_code)]
46    name: String,
47}
48
49/// 用于标记 summer_boot web 的入口点
50/// # Examples
51/// ```
52/// #[summer_boot::main]
53/// async fn main() {
54///     async { println!("Hello world"); }.await
55/// }
56/// ```
57#[proc_macro_attribute]
58pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
59    let mut input = parse_macro_input!(item as ItemFn);
60    let attrs = &input.attrs;
61    let vis = &input.vis;
62    let sig = &mut input.sig;
63    let body = &input.block;
64    let _name = &sig.ident;
65
66    if sig.asyncness.is_none() {
67        return syn::Error::new_spanned(sig.fn_token, "仅支持 async fn")
68            .to_compile_error()
69            .into();
70    }
71    sig.asyncness = None;
72
73    (quote! {
74        #(#attrs)*
75        #vis #sig {
76            summer_boot::rt::SummerRuntime::new()
77            .block_on(async move { #body });
78        }
79    })
80    .into()
81}
82
83/// 完成 summer_boot 项目下的自动扫描功能,会先扫描找到`summer_boot::run();`
84/// 函数,然后在此处进行装配活动。也可以手动增加过滤路径或过滤文件。
85/// 如果增加过滤路径,需要在末尾添加 `/`,如果增加过滤文件,需要在末尾添加 `.rs`。
86///
87/// 注意:如果需要在此处添加运行时,必须在当前宏的后面配置,否则无法完成装配
88/// # Examples
89/// ```rust
90/// // #[summer_boot::auto_scan]
91/// // #[summer_boot::auto_scan("summer-boot-tests/src/lib.rs")]
92/// fn main() {
93///     summer_boot::run();
94/// }
95/// ```
96#[proc_macro_attribute]
97pub fn auto_scan(args: TokenStream, input: TokenStream) -> TokenStream {
98    let mut project = Vec::<String>::new();
99    let mut filter_paths = Vec::<String>::new();
100    // 找到需要扫描的路径
101    let mut cargo_toml = fs::File::open("Cargo.toml").expect("Cargo Toml文件找不到");
102    let mut content = String::new();
103
104    cargo_toml
105        .read_to_string(&mut content)
106        .expect("Cargo内容为空");
107
108    // 根据包类型分别处理
109    if let Ok(conf_work_space) = toml::from_str::<ConfWorkSpace>(&content) {
110        if let Some(workspace) = conf_work_space.workspace {
111            if let Some(members) = workspace.members {
112                for member in members {
113                    project.push(format!("{}/{}", member, "src"));
114                }
115            }
116        } else if project.len() == 0 {
117            if let Some(_) = conf_work_space.package {
118                project.push("src".to_string());
119            }
120        }
121    }
122
123    // 解析宏信息
124    let args = parse_macro_input!(args as AttributeArgs);
125    for arg in args {
126        if let NestedMeta::Lit(Lit::Str(project)) = arg {
127            filter_paths.push(project.value());
128        }
129    }
130
131    // 解析函数体
132    let mut input = parse_macro_input!(input as ItemFn);
133
134    // 查找主函数的位置和是否存在变量名
135    // 未找到则直接退出宏处理
136    // 变量名不存在则默认添加app
137    // 如果存在则会返回出来,供后续使用
138    if let Some((master_index, master_name)) = scan_master_fn(&mut input) {
139        // 解析yaml文件
140        let mut listener_addr = String::from("0.0.0.0:");
141        let mut app_context_path = String::from("");
142        let config = summer_boot_autoconfigure::load_conf();
143        if let Some(config) = config {
144            let read_server = serde_json::to_string(&config.server).expect("读取服务配置文件失败");
145            let v: Value = serde_json::from_str(&read_server).expect("读取服务配置文件失败");
146            let port = v["port"].to_string();
147            let context_path = v["context_path"].to_string();
148            listener_addr.push_str(&port);
149            app_context_path.push_str(&context_path);
150        }
151
152        // 开始扫描
153        for path in project {
154            scan_method(
155                &path,
156                &filter_paths,
157                &mut input,
158                &app_context_path,
159                (master_index, &master_name),
160            );
161        }
162
163        // 配置listen
164        input.block.stmts.push(parse_quote! {
165            #master_name.listen(#listener_addr).await.expect("配置listen失败");
166        });
167    }
168
169    // 构建新的函数结构,增加函数行
170    TokenStream::from(input.into_token_stream())
171}
172
173// 扫描函数,找到主函数
174// 返回主函数所在的位置索引,并判断是否存在变量名
175// 如果存在,则找到并返回
176// 如果不存在,则删除默认主函数,添加新的主函数
177fn scan_master_fn(input: &mut ItemFn) -> Option<(i32, Ident)> {
178    let mut master_index: i32 = -1;
179    let mut master_name = Ident::new("app", Span::call_site());
180
181    for (index, stmt) in (&mut input.block.stmts).iter_mut().enumerate() {
182        let master = stmt.to_token_stream().to_string();
183        if let Some(_) = master.find("summer_boot :: run()") {
184            master_index = index as i32;
185        }
186    }
187    if master_index < 0 {
188        None
189    } else {
190        if let Stmt::Local(local) = &input.block.stmts[master_index as usize] {
191            // 函数存在变量,需要获取变量名称
192            let pat = &local.pat;
193
194            if let Pat::Ident(pat_ident) = pat {
195                let name = pat_ident.ident.to_string();
196                master_name = Ident::new(&name, Span::call_site());
197            }
198        } else {
199            // 函数不存在变量,需要手动添加
200            // TODO 目前相对简单,删除当前函数,并添加指定的函数即可,后续建议修改
201            input.block.stmts.remove(master_index as usize);
202            input.block.stmts.insert(
203                master_index as usize,
204                parse_quote! {
205                    let mut app = summer_boot::run();
206                },
207            )
208        }
209
210        Some((master_index, master_name))
211    }
212}
213
214// 判断是否是目录,如果是路径则需要循环递归处理,
215// 如果是文件则直接处理
216// 处理过程中会将函数调用函数拼接,然后插入到指定的位置 下标+1 的位置
217fn scan_method(
218    path: &str,
219    filter_paths: &Vec<String>,
220    input_token_stream: &mut ItemFn,
221    context_path: &str,
222    (mut master_index, master_name): (i32, &Ident),
223) {
224    if let Ok(entries) = fs::read_dir(path) {
225        for entry in entries {
226            if let Ok(entry) = entry {
227                let file_path = entry.path();
228                if file_path.is_file() {
229                    if let Some(extension) = file_path.extension() {
230                        if extension == "rs" {
231                            if filter_paths.iter().any(|p| path.contains(p)) {
232                                return;
233                            }
234                            // 如果是文件,则处理内部细节
235                            let content = fs::read_to_string(entry.path()).expect("处理内部细节");
236                            // 解析文件
237                            let ast = parse_file(&content).expect("解析文件失败");
238                            let items = ast.items;
239                            for item in items {
240                                if let Item::Fn(item) = item {
241                                    // 处理函数中的函数名,指定宏信息
242                                    for attr in item.attrs {
243                                        // 遍历所有宏信息
244                                        if let Meta::List(meta) =
245                                            attr.parse_meta().expect("所有所有宏信息")
246                                        {
247                                            // 判断宏是否为指定的宏
248                                            let attr_path = meta.path.to_token_stream().to_string();
249
250                                            let method = config_req_type(&attr_path);
251                                            if method.is_none() {
252                                                continue;
253                                            }
254                                            let method =
255                                                method.expect("是否为指定的宏").to_token_stream();
256
257                                            // 获取函数全路径名
258                                            let fn_name: &String = &item.sig.ident.to_string();
259                                            let fn_path_token_stream = config_function_path(
260                                                &file_path.to_str().unwrap_or("文件为空"),
261                                                fn_name,
262                                            );
263
264                                            // 如果是 summer_boot 的宏信息,则处理
265                                            let attr_url = meta
266                                                .nested
267                                                .into_iter()
268                                                .next()
269                                                .expect("summer_boot 的宏信息");
270                                            if let NestedMeta::Lit(Lit::Str(url)) = attr_url {
271                                                let url = url.value();
272                                                let url = format!("{}{}", context_path, url)
273                                                    .replace("\"", "")
274                                                    .replace("//", "/");
275
276                                                if input_token_stream.block.stmts.len() < 1 {
277                                                    // 如果注入的方法中没有任何代码,则不操作
278                                                    break;
279                                                } else {
280                                                    // 添加,注意下标加 1
281                                                    master_index += 1;
282                                                    input_token_stream.block.stmts.insert(
283                                                    master_index as usize,
284                                                    parse_quote! {
285                                                        #master_name.at(#url).#method(#fn_path_token_stream);
286                                                    },
287                                                );
288                                                }
289                                            }
290                                        }
291                                    }
292                                }
293                            }
294                        }
295                    }
296                }
297            }
298        }
299    }
300}
301
302// 配置函数全路径
303// 根据相对项目的绝对路径找到函数调用的全路径链
304// 注意:目前无法完成文件中mod下的函数调用,无法找到
305fn config_function_path(path: &str, fu_name: &str) -> proc_macro2::TokenStream {
306    let mut fn_path_idents = Punctuated::<Ident, Token![::]>::new();
307    fn_path_idents.push(Ident::new("crate", Span::call_site()));
308
309    // 配置函数路径
310    let names: Vec<&str> = path
311        [path.find("src").expect("转换src") + 4..path.rfind(".rs").expect("转换rs后缀")]
312        .split("/")
313        .collect();
314
315    let len = names.len();
316    for (index, name) in names.into_iter().enumerate() {
317        if (index + 1) == len {
318            // 最后一个文件名称如果是main、lib、test则不需要加入路径
319            match name {
320                "main" | "mod" | "lib" => {
321                    break;
322                }
323                _ => {}
324            }
325        }
326        if !name.is_empty() {
327            // 配置文件包名
328            fn_path_idents.push(Ident::new(name, Span::call_site()));
329        }
330    }
331    // 配置函数名称
332    fn_path_idents.push(Ident::new(fu_name, Span::call_site()));
333
334    fn_path_idents.to_token_stream()
335}
336
337// 配置请求类型
338fn config_req_type(attr_path: &str) -> Option<Ident> {
339    if attr_path == "summer_boot_macro :: get"
340        || attr_path == "summer_boot :: get"
341        || attr_path == "get"
342        || attr_path == "summer_boot_macro :: head"
343        || attr_path == "summer_boot :: head"
344        || attr_path == "head"
345        || attr_path == "summer_boot_macro :: put"
346        || attr_path == "summer_boot :: put"
347        || attr_path == "put"
348        || attr_path == "summer_boot_macro :: post"
349        || attr_path == "summer_boot :: post"
350        || attr_path == "post"
351        || attr_path == "summer_boot_macro :: delete"
352        || attr_path == "summer_boot :: delete"
353        || attr_path == "delete"
354        || attr_path == "summer_boot_macro :: head"
355        || attr_path == "summer_boot :: head"
356        || attr_path == "head"
357        || attr_path == "summer_boot_macro :: options"
358        || attr_path == "summer_boot :: options"
359        || attr_path == "options"
360        || attr_path == "summer_boot_macro :: connect"
361        || attr_path == "summer_boot :: connect"
362        || attr_path == "connect"
363        || attr_path == "summer_boot_macro :: patch"
364        || attr_path == "summer_boot :: patch"
365        || attr_path == "patch"
366        || attr_path == "summer_boot_macro :: trace"
367        || attr_path == "summer_boot :: trace"
368        || attr_path == "trace"
369    {
370        if attr_path.starts_with("summer_boot_macro ::") {
371            return Some(Ident::new(
372                &attr_path["summer_boot_macro :: ".len()..],
373                Span::call_site(),
374            ));
375        } else if attr_path.starts_with("summer_boot ::") {
376            return Some(Ident::new(
377                &attr_path["summer_boot :: ".len()..],
378                Span::call_site(),
379            ));
380        } else {
381            return Some(Ident::new(attr_path, Span::call_site()));
382        }
383    } else {
384        return None;
385    }
386}
387
388macro_rules! doc_comment {
389    ($x:expr; $($tt:tt)*) => {
390        #[doc = $x]
391        $($tt)*
392    };
393}
394
395macro_rules! method_macro {
396    (
397        $($method:ident,)+
398    ) => {
399        $(doc_comment! {
400concat!("
401# 功能
402创建路由接口,用于`summer_boot.new()`的返回值使用,
403该函数提供了对应方法`summer_boot/src/web2/gateway/routes.rs`文件下的所有路由方法,
404
405# 支持的路由如下:
406- get
407- head
408- put
409- post
410- delete
411- options
412- connect
413- patch
414- trace
415
416# 例子:
417```rust
418# use summer_boot::{Request, Result};
419#[summer_boot_macro::", stringify!($method), r#"("/")]
420async fn example(mut req: Request<()>) -> Result {
421    Ok(format!("Hello World").into())
422}
423```
424"#);
425            #[proc_macro_attribute]
426            pub fn $method(_args: TokenStream, input: TokenStream) -> TokenStream {
427
428                let mut input = parse_macro_input!(input as ItemFn);
429                let attrs = &input.attrs;
430                let vis = &input.vis;
431                let sig = &mut input.sig;
432                let body = &input.block;
433                let _name = &sig.ident;
434                if sig.asyncness.is_none() {
435                    return syn::Error::new_spanned(sig.fn_token, "仅支持 async fn")
436                        .to_compile_error()
437                        .into();
438                }
439
440                (quote! {
441                    #(#attrs)*
442                    #vis #sig
443                        #body
444                }).into()
445            }
446        })+
447    };
448}
449
450method_macro!(get, head, put, post, delete, patch, trace, options, connect,);