Skip to main content

miko_macros/
lib.rs

1use crate::route::RouteAttr;
2use crate::route::core::route_handler;
3use crate::toolkit::attr::StrAttrMap;
4#[cfg(feature = "auto")]
5use crate::toolkit::impl_operation::{get_constructor, inject_deps};
6use crate::toolkit::rout_arg::{
7    FnArgResult, IntoFnArgs, RouteFnArg, build_clone_stmt, build_config_value_injector,
8    build_dep_injector,
9};
10use proc_macro::TokenStream;
11use quote::{format_ident, quote};
12use syn::{ItemFn, ItemMod, parse_macro_input};
13
14mod extractor;
15mod mod_transform;
16mod route;
17mod toolkit;
18
19#[cfg(feature = "utoipa")]
20mod utoipa;
21
22/// 标准路由属性宏(用于自定义路由)
23///
24/// 用法:在处理请求的函数上使用 `#[route(...)]` 或派生宏如 `#[get(...)]`。
25/// 该宏根据属性(path/method)和参数注解生成路由处理器。
26///
27/// 参数标注:
28/// - `#[path]`:从路径中提取(如 `/users/{id}`);
29/// - `#[query]`:从查询字符串构建结构并注入;
30/// - `#[body]`:从请求体反序列化(默认 JSON;标记 `str` 可保留为 String);
31/// - `#[dep]`:注入全局依赖(参数类型通常为 `Arc<T>`,需先注册该组件);
32/// - `#[config("key")]`/`#[config(path = "key")]`:从应用配置读取并解析为参数类型。
33/// - `#[desc("描述")]`:为参数添加描述(启用 utoipa 时会生成 OpenAPI 文档);
34///
35/// 注意:
36/// - 仅当同时启用 `auto` feature 且应用通过 `#[miko]` 启动时,框架才会自动收集并注册由这些宏生成的路由;
37/// - 若未启用 `auto`,`route`/派生宏及 `#[dep]` 不会触发框架级的自动注册或依赖注入——此时需要在你的初始化代码中手动注册路由与依赖;
38///
39/// 建议:处理器应声明为 `async fn`;若未显式返回类型,宏会自动设置为实现 `IntoResponse` 的类型。
40///
41/// 示例:
42/// ```rust,ignore
43/// #[get("/hello/{id}")]
44/// async fn hello(
45///     #[path] #[desc("用户ID")] id: i32
46/// ) -> impl miko::http::response::into_response::IntoResponse {
47///     // 处理请求
48/// }
49/// ```
50#[proc_macro_attribute]
51pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
52    let args = parse_macro_input!(attr as RouteAttr);
53    let fn_item = parse_macro_input!(item as ItemFn);
54    route_handler(args, fn_item)
55}
56
57/// # Miko宏
58/// 自动配置
59/// - 展开出#\[tokio::main]
60/// - 注册依赖[仅限auto]
61/// - 加载配置到_config
62/// - 新建router: Router
63/// - > 用户代码
64/// - 收集定义#\[get]等宏定义的路由并注册
65/// - 运行app
66#[proc_macro_attribute]
67pub fn miko(attr: TokenStream, item: TokenStream) -> TokenStream {
68    let input_fn = parse_macro_input!(item as ItemFn);
69    let fn_name = &input_fn.sig.ident;
70    let fn_vis = &input_fn.vis;
71    let str_attr_map = parse_macro_input!(attr as StrAttrMap);
72    let user_statements = &input_fn.block.stmts;
73    let set_panic_hook = if str_attr_map.map.contains_key("sse") {
74        Some(quote! {
75            ::miko::http::response::sse::set_sse_panic_hook();
76        })
77    } else {
78        None
79    };
80    let catch_panic = if str_attr_map.map.contains_key("catch") {
81        if cfg!(feature = "catch_panic") {
82            Some(quote! {
83                router.with_catch_panic();
84            })
85        } else {
86            return quote! {
87                compile_error!("`catch` attribute requires `catch_panic` feature to be enabled");
88            }
89            .into();
90        }
91    } else {
92        None
93    };
94    let build_sign = str_attr_map.map.contains_key("build");
95    if build_sign {
96        quote! {
97            #fn_vis async fn #fn_name() -> ::miko::app::Application {
98                #set_panic_hook
99                let mut _config = ::miko::app::config::ServerSettings::from_global_settings();
100                let mut router = ::miko::router::Router::new();
101                #catch_panic
102
103                #( #user_statements )*
104
105                router.merge(::miko::auto::collect_global_router());
106                ::miko::app::Application::new(_config, router.take())
107            }
108        }
109    } else {
110        quote! {
111            #[::miko::tokio::main]
112            async fn main() {
113                #set_panic_hook
114                let mut _config = ::miko::app::config::ServerSettings::from_global_settings();
115                let mut router = ::miko::router::Router::new();
116                #catch_panic
117
118                #( #user_statements )*
119
120                router.merge(::miko::auto::collect_global_router());
121                let app = ::miko::app::Application::new(_config, router.take());
122                app.run().await.unwrap();
123            }
124        }
125    }
126    .into()
127}
128macro_rules! derive_route_macro {
129    ($macro_name: ident, $method_ident:ident) => {
130        #[doc = concat!("简写:等价于 `#[route(..., method = \"", stringify!($method_ident), "\" )]`。\n\n",
131                         "仅当启用 `auto` feature 且应用通过 `#[miko]` 启动时,框架才会自动注册由该宏生成的路由;\n",
132                         "否则该宏仅生成处理函数,路由需在初始化代码中手动注册。")]
133        #[proc_macro_attribute]
134        pub fn $macro_name(attr: TokenStream, item: TokenStream) -> TokenStream {
135            let mut args = syn::parse_macro_input!(attr as RouteAttr);
136            let fn_item = syn::parse_macro_input!(item as ItemFn);
137            let method_to_add = ::hyper::Method::$method_ident;
138            match &mut args.method {
139                Some(existing_methods) => {
140                    existing_methods.push(method_to_add);
141                }
142                None => {
143                    args.method = Some(vec![method_to_add]);
144                }
145            }
146            route_handler(args, fn_item)
147        }
148    };
149}
150
151derive_route_macro!(get, GET);
152derive_route_macro!(post, POST);
153derive_route_macro!(put, PUT);
154derive_route_macro!(delete, DELETE);
155derive_route_macro!(patch, PATCH);
156derive_route_macro!(head, HEAD);
157derive_route_macro!(options, OPTIONS);
158derive_route_macro!(trace, TRACE);
159derive_route_macro!(connect, CONNECT);
160
161#[cfg(feature = "auto")]
162/// 组件宏:将 `impl` 中的构造函数注册为可由框架管理的可注入组件。
163///
164/// 使用:
165/// - 在 `impl` 上添加 `#[component]`(可带 `prewarm`)以将该类型注册为预热组件;
166/// - 使用 `#[component(request)]` 声明每个 HTTP 请求内复用的组件;
167/// - 使用 `#[component(transient)]` 声明每次解析都重新创建的组件;
168/// - 构造函数应为 `async fn new(...) -> Self`;`Arc<T>` 可解析任意生命周期,按值 `T` 仅可解析 transient 组件;
169/// - 注册后的组件可在处理器参数上使用 `#[dep]` 标注注入(当启用 `auto` 时)。
170///
171/// `prewarm` 生效条件:仅在应用通过 `#[miko]` 启动(并启用 `auto`)时才会在启动阶段触发预热。
172///
173/// 示例:
174/// ```rust,ignore
175/// #[component(prewarm)]
176/// impl MyService {
177///     async fn new(dep: std::sync::Arc<Other>) -> Self { /* ... */ }
178/// }
179///
180/// // 在处理器中注入:
181/// async fn handler(#[dep] svc: std::sync::Arc<MyService>) { /* ... */ }
182/// ```
183#[proc_macro_attribute]
184pub fn component(attr: TokenStream, input: TokenStream) -> TokenStream {
185    use quote::format_ident;
186    use syn::{ItemImpl, TypePath};
187    let args = syn::parse_macro_input!(attr as StrAttrMap);
188    let input_struct = parse_macro_input!(input as ItemImpl);
189    let prewarm = args.get("prewarm").is_some();
190    let mut lifetime = "singleton".to_string();
191    let mut lifetime_specified = false;
192
193    let mut set_lifetime = |mode: &str| {
194        let normalized = mode.to_ascii_lowercase();
195        match normalized.as_str() {
196            "singleton" | "request" | "transient" => {
197                if lifetime_specified && lifetime != normalized {
198                    panic!(
199                        "Conflicting #[component] lifetime: both '{}' and '{}' specified",
200                        lifetime, normalized
201                    );
202                }
203                lifetime = normalized;
204                lifetime_specified = true;
205            }
206            _ => panic!(
207                "Invalid #[component] lifetime '{}'. Expected `singleton`, `request`, or `transient`.",
208                mode
209            ),
210        }
211    };
212
213    if let Some(mode) = args.get("mode") {
214        set_lifetime(mode);
215    } else if let Some(default_mode) = args.default.as_ref() {
216        set_lifetime(default_mode);
217    }
218
219    if args.map.contains_key("singleton") {
220        set_lifetime("singleton");
221    }
222    if args.map.contains_key("transient") {
223        set_lifetime("transient");
224    }
225    if args.map.contains_key("request") {
226        set_lifetime("request");
227    }
228
229    if prewarm && lifetime != "singleton" {
230        panic!("`#[component(prewarm)]` is only valid for singleton components");
231    }
232
233    let lifetime_tokens = match lifetime.as_str() {
234        "singleton" => quote!(::miko::dependency_container::DependencyLifetime::Singleton),
235        "request" => quote!(::miko::dependency_container::DependencyLifetime::Request),
236        "transient" => quote!(::miko::dependency_container::DependencyLifetime::Transient),
237        _ => unreachable!(),
238    };
239    let mut depend_get_stmts = Vec::new();
240    let mut arg_idents = Vec::new();
241    let type_ident = match *input_struct.self_ty.clone() {
242        syn::Type::Path(TypePath { path, .. }) => path
243            .segments
244            .last()
245            .map(|seg| seg.ident.clone())
246            .unwrap_or_else(|| format_ident!("UnknowType")),
247        _ => format_ident!("UnknowType"),
248    };
249    if let Some(method) = get_constructor(&input_struct.items) {
250        if method.sig.asyncness.is_none() {
251            panic!("service method new must be async")
252        }
253        let args = &method.sig.inputs;
254        inject_deps(args, &mut depend_get_stmts, &mut arg_idents);
255    }
256    quote! {
257        #input_struct
258        ::miko::inventory::submit! {
259            ::miko::dependency_container::DependencyDefFn(|| {
260                ::miko::dependency_container::DependencyDef {
261                    type_id: std::any::TypeId::of::<#type_ident>(),
262                    type_name: std::any::type_name::<#type_ident>(),
263                    prewarm: #prewarm,
264                    name: "___",
265                    lifetime: #lifetime_tokens,
266                    init_fn: |__resolve_context| {
267                        Box::pin(async move {
268                            #(#depend_get_stmts)*
269                            let val: #type_ident = #type_ident::new(#(#arg_idents),*).await;
270                            Ok(
271                                ::std::boxed::Box::new(val)
272                                    as ::std::boxed::Box<dyn ::std::any::Any + Send + Sync>
273                            )
274                        })
275                    }
276                }
277            })
278        }
279    }
280    .into()
281}
282
283// ==================== Utoipa 辅助宏 ====================
284
285#[cfg(feature = "utoipa")]
286/// 标记响应信息
287///
288/// 用法:
289/// ```rust,ignore
290/// #[u_response(status = 404, description = "用户不存在", body = ErrorResponse)]
291/// ```
292#[proc_macro_attribute]
293pub fn u_response(_attr: TokenStream, item: TokenStream) -> TokenStream {
294    // 这个宏不做任何转换,只是作为标记供 route 宏读取
295    item
296}
297
298#[cfg(feature = "utoipa")]
299/// 标记 API 标签
300///
301/// 用法:
302/// ```rust,ignore
303/// #[u_tag("用户管理")]
304/// ```
305#[proc_macro_attribute]
306pub fn u_tag(_attr: TokenStream, item: TokenStream) -> TokenStream {
307    item
308}
309
310#[cfg(feature = "utoipa")]
311/// 标记 API 摘要
312///
313/// 用法:
314/// ```rust,ignore
315/// #[u_summary("获取用户信息")]
316/// ```
317#[proc_macro_attribute]
318pub fn u_summary(_attr: TokenStream, item: TokenStream) -> TokenStream {
319    item
320}
321
322#[cfg(feature = "utoipa")]
323/// 标记 API 详细描述
324///
325/// 用法:
326/// ```rust,ignore
327/// #[u_description("根据用户 ID 获取详细信息")]
328/// ```
329#[proc_macro_attribute]
330pub fn u_description(_attr: TokenStream, item: TokenStream) -> TokenStream {
331    item
332}
333
334#[cfg(feature = "utoipa")]
335/// 标记自定义请求体
336///
337/// 用于处理无法自动推断的请求体类型,比如 `Multipart`。
338/// 当使用此宏时,会覆盖自动推断的请求体配置。
339///
340/// 参数:
341/// - `content`: 请求体的类型(必需)
342/// - `content_type`: Content-Type 头(可选,默认为 "application/json")
343/// - `description`: 请求体描述(可选)
344///
345/// 用法:
346/// ```rust,ignore
347/// use miko::http::extractor::Multipart;
348///
349/// #[post("/upload")]
350/// #[u_request_body(
351///     content = Multipart,
352///     content_type = "multipart/form-data",
353///     description = "文件上传"
354/// )]
355/// async fn upload_file(multipart: Multipart) -> impl IntoResponse {
356///     // 处理文件上传
357/// }
358/// ```
359#[proc_macro_attribute]
360pub fn u_request_body(_attr: TokenStream, item: TokenStream) -> TokenStream {
361    item
362}
363
364#[cfg(feature = "utoipa")]
365/// 标记参数补充信息
366///
367/// 用法:
368/// ```rust,ignore
369/// #[u_param(name = "id", description = "用户ID", example = 123)]
370/// ```
371#[proc_macro_attribute]
372pub fn u_param(_attr: TokenStream, item: TokenStream) -> TokenStream {
373    item
374}
375
376#[cfg(feature = "utoipa")]
377/// 标记 API 已弃用
378///
379/// 用法:
380/// ```rust,ignore
381/// #[u_deprecated]
382/// ```
383#[proc_macro_attribute]
384pub fn u_deprecated(_attr: TokenStream, item: TokenStream) -> TokenStream {
385    item
386}
387
388/// 为参数添加描述
389///
390/// 用于给函数参数添加描述信息,在启用 utoipa feature 时会生成到 OpenAPI 文档中。
391///
392/// 用法:
393/// ```rust,ignore
394/// #[get("/users/{id}")]
395/// async fn get_user(
396///     #[path] #[desc("用户ID")] id: i32,
397///     #[query] #[desc("页码")] page: Option<i32>
398/// ) -> impl IntoResponse {
399///     // ...
400/// }
401/// ```
402#[proc_macro_attribute]
403pub fn desc(_attr: TokenStream, item: TokenStream) -> TokenStream {
404    // 这个宏不做任何转换,只是作为标记供其他宏读取
405    item
406}
407
408// 防止覆盖 builtin 宏
409// /// 标记路径参数
410// ///
411// /// 用于标记从 URL 路径中提取的参数。
412// ///
413// /// 用法:
414// /// ```rust,ignore
415// /// #[get("/users/:id")]
416// /// async fn get_user(#[path] id: i32) -> impl IntoResponse {
417// ///     // ...
418// /// }
419// /// ```
420// #[proc_macro_attribute]
421// pub fn path(_attr: TokenStream, item: TokenStream) -> TokenStream {
422//     // 这个宏不做任何转换,只是作为标记供 route 宏读取
423//     item
424// }
425
426/// 标记查询参数
427///
428/// 用于标记从 URL 查询字符串中提取的参数。
429///
430/// 用法:
431/// ```rust,ignore
432/// #[get("/users")]
433/// async fn list_users(
434///     #[query] page: Option<i32>,
435///     #[query] page_size: Option<i32>
436/// ) -> impl IntoResponse {
437///     // ...
438/// }
439/// ```
440#[proc_macro_attribute]
441pub fn query(_attr: TokenStream, item: TokenStream) -> TokenStream {
442    // 这个宏不做任何转换,只是作为标记供 route 宏读取
443    item
444}
445
446/// 标记请求体参数
447///
448/// 用于标记从请求体中提取的参数。
449///
450/// 用法:
451/// ```rust,ignore
452/// #[post("/users")]
453/// async fn create_user(#[body] user: User) -> impl IntoResponse {
454///     // ...
455/// }
456/// ```
457#[proc_macro_attribute]
458pub fn body(_attr: TokenStream, item: TokenStream) -> TokenStream {
459    // 这个宏不做任何转换,只是作为标记供 route 宏读取
460    item
461}
462
463/// 标记 Tower Layer
464///
465/// 用于在路由处理函数或模块上应用 Tower Layer 中间件。
466///
467/// **在函数上使用:** 可以使用多个 `#[layer]` 属性,它们将从上到下声明,从内到外应用。
468/// **在模块上使用:** 为模块内的所有路由自动添加指定的 layer。
469///
470/// 用法:
471/// ```rust,ignore
472/// use tower_http::timeout::TimeoutLayer;
473/// use std::time::Duration;
474///
475/// // 单个 layer(函数级)
476/// #[get("/users/{id}")]
477/// #[layer(TimeoutLayer::new(Duration::from_secs(30)))]
478/// async fn get_user(#[path] id: i32) -> impl IntoResponse {
479///     // ...
480/// }
481///
482/// // 多个 layer(函数级)
483/// #[post("/users")]
484/// #[layer(TimeoutLayer::new(Duration::from_secs(30)))]
485/// #[layer(CompressionLayer::new())]
486/// async fn create_user(#[body] user: User) -> impl IntoResponse {
487///     // 调用链: CompressionLayer -> TimeoutLayer -> handler
488/// }
489///
490/// // 模块级 layer
491/// #[layer(AuthLayer::new())]
492/// mod protected {
493///     #[get("/data")]
494///     async fn get_data() { }  // 自动应用 AuthLayer
495/// }
496/// ```
497#[proc_macro_attribute]
498pub fn layer(attr: TokenStream, item: TokenStream) -> TokenStream {
499    if let Ok(mut mod_item) = syn::parse::<ItemMod>(item.clone()) {
500        let layer_attr = parse_macro_input!(attr as mod_transform::ModLayerAttr);
501        mod_transform::apply_transform_to_module(
502            &mut mod_item,
503            mod_transform::TransformOp::Layer(layer_attr.expr),
504        );
505        return quote! { #mod_item }.into();
506    }
507    item
508}
509
510#[cfg(feature = "utoipa")]
511/// 仅生成 OpenAPI 文档,不自动注册路由
512///
513/// 用于手动注册路由的场景。该宏生成 utoipa::path 属性,但不会通过 inventory 自动注册路由。
514/// 你需要手动将这个函数注册到 Router 中。
515///
516/// 与 `#[get]`, `#[post]` 等宏的区别:
517/// - `#[get]` 等宏: 自动注册路由 + 生成 OpenAPI (需要 auto feature)
518/// - `#[miko_path]`: 只生成 OpenAPI,需要手动注册路由
519///
520/// 用法:
521/// ```rust,ignore
522/// // 1. 使用 miko_path 宏生成 OpenAPI
523/// #[miko::miko_path(path = "/manual")]
524/// #[u_tag("Manual")]
525/// #[u_response(status = 200, body = User)]
526/// async fn manual_handler() -> Json<User> {
527///     // ...
528/// }
529///
530/// // 2. 手动注册路由
531/// let router = Router::new()
532///     .get("/manual", manual_handler);
533///
534/// // 3. OpenApi 定义中可以引用
535/// #[derive(miko::OpenApi)]
536/// #[openapi(
537///     paths(manual_handler),  // 仍然可以在这里引用
538/// )]
539/// struct ApiDoc;
540/// ```
541#[proc_macro_attribute]
542pub fn miko_path(attr: TokenStream, item: TokenStream) -> TokenStream {
543    // 解析 HTTP 方法和路径
544    // 格式: #[miko_path(path = "/xxx")] 或 #[miko_path(path = "/xxx", ...)]
545    let args = parse_macro_input!(attr as RouteAttr);
546    let fn_item = parse_macro_input!(item as ItemFn);
547
548    // 为了简化,我们生成一个不带 inventory 的版本
549    use crate::route::core::route_handler_no_register;
550    route_handler_no_register(args, fn_item)
551}
552
553/// # Prefix 宏:模块路由前缀
554///
555/// 用法:在 `mod` 块上使用 `#[prefix("/api")]`,会自动为模块内的所有路由添加前缀。
556///
557/// **注意:和Router::nest不同,prefix只是简单地在内部路由路径前添加前缀,并不会将运行时内部路由获取到的路径进行修改。**
558///
559/// 行为:
560/// - 对模块内直接的函数(如果有路由宏)添加路径前缀
561/// - 对模块内的嵌套 mod 也应用相同的前缀(如果嵌套 mod 内部没有 prefix,会继续附加)
562/// - 支持路径的自动合并(处理多余的斜杠)
563///
564/// 示例:
565/// ```rust,ignore
566/// #[prefix("/api")]
567/// mod api {
568///     #[get("/users")]
569///     async fn get_users() { }  // 实际注册为 GET /api/users
570/// }
571/// ```
572#[proc_macro_attribute]
573pub fn prefix(attr: TokenStream, item: TokenStream) -> TokenStream {
574    let prefix_attr = parse_macro_input!(attr as mod_transform::PrefixAttr);
575    let mut mod_item = parse_macro_input!(item as ItemMod);
576    mod_transform::apply_transform_to_module(
577        &mut mod_item,
578        mod_transform::TransformOp::Prefix(prefix_attr.path),
579    );
580    quote! { #mod_item }.into()
581}
582
583/// 中间件
584#[proc_macro_attribute]
585pub fn middleware(_attr: TokenStream, item: TokenStream) -> TokenStream {
586    let mut input_fn = parse_macro_input!(item as ItemFn);
587    let fn_name = &input_fn.sig.ident;
588    let vis = &input_fn.vis;
589    let block = &input_fn.block;
590    let attrs = &input_fn.attrs;
591    let args = RouteFnArg::from_punctuated(&mut input_fn.sig.inputs);
592    let mut req_ident = format_ident!("_req");
593    let mut next_ident = format_ident!("_next");
594    let mut config_stmts = Vec::new();
595    let mut deps_stmts = Vec::new();
596    let mut clone_stmts = Vec::new();
597    let outer_args = args.gen_fn_args(|rfa| {
598        // 判断是否是req, next
599        if let syn::Type::Path(path) = &rfa.ty {
600            if path.path.segments.last().unwrap().ident == "Req" {
601                req_ident = rfa.ident.clone();
602                return FnArgResult::Remove;
603            } else if path.path.segments.last().unwrap().ident == "Next" {
604                next_ident = rfa.ident.clone();
605                return FnArgResult::Remove;
606            }
607        }
608        // 判断是否是 #[config] #[dep]
609        if !rfa.mark.is_empty() {
610            if rfa.marked_by("config") || rfa.marked_by("dep") {
611                return FnArgResult::Remove;
612            } else {
613                panic!("middleware only support mark #[config] or #[dep]");
614            }
615        }
616        // 其余变量
617        build_clone_stmt(rfa, &mut clone_stmts);
618        FnArgResult::Keep
619    });
620    build_dep_injector(&args, &req_ident, &mut deps_stmts);
621    build_config_value_injector(&args, &mut config_stmts);
622    let mut inputs = input_fn.sig.inputs;
623    inputs.clear();
624    inputs.extend(outer_args);
625    quote! {
626        #(#attrs)*
627        #vis fn #fn_name (#inputs) -> ::miko::middleware::FromFnLayer<impl Fn(::miko::miko_core::Req, ::miko::middleware::Next) -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ::miko::AppResult<::miko::miko_core::Resp>> + Send>> + Clone> {
628            ::miko::middleware::middleware_from_fn(move |#req_ident: ::miko::miko_core::Req, #next_ident: ::miko::middleware::Next| {
629                #( #clone_stmts )*
630                Box::pin(async move {
631                    #( #deps_stmts )*
632                    #( #config_stmts )*
633                    #block
634                }) as ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ::miko::AppResult<::miko::miko_core::Resp>> + Send>>
635            })
636        }
637    }.into()
638}