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#[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#[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#[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#[cfg(feature = "utoipa")]
286#[proc_macro_attribute]
293pub fn u_response(_attr: TokenStream, item: TokenStream) -> TokenStream {
294 item
296}
297
298#[cfg(feature = "utoipa")]
299#[proc_macro_attribute]
306pub fn u_tag(_attr: TokenStream, item: TokenStream) -> TokenStream {
307 item
308}
309
310#[cfg(feature = "utoipa")]
311#[proc_macro_attribute]
318pub fn u_summary(_attr: TokenStream, item: TokenStream) -> TokenStream {
319 item
320}
321
322#[cfg(feature = "utoipa")]
323#[proc_macro_attribute]
330pub fn u_description(_attr: TokenStream, item: TokenStream) -> TokenStream {
331 item
332}
333
334#[cfg(feature = "utoipa")]
335#[proc_macro_attribute]
360pub fn u_request_body(_attr: TokenStream, item: TokenStream) -> TokenStream {
361 item
362}
363
364#[cfg(feature = "utoipa")]
365#[proc_macro_attribute]
372pub fn u_param(_attr: TokenStream, item: TokenStream) -> TokenStream {
373 item
374}
375
376#[cfg(feature = "utoipa")]
377#[proc_macro_attribute]
384pub fn u_deprecated(_attr: TokenStream, item: TokenStream) -> TokenStream {
385 item
386}
387
388#[proc_macro_attribute]
403pub fn desc(_attr: TokenStream, item: TokenStream) -> TokenStream {
404 item
406}
407
408#[proc_macro_attribute]
441pub fn query(_attr: TokenStream, item: TokenStream) -> TokenStream {
442 item
444}
445
446#[proc_macro_attribute]
458pub fn body(_attr: TokenStream, item: TokenStream) -> TokenStream {
459 item
461}
462
463#[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#[proc_macro_attribute]
542pub fn miko_path(attr: TokenStream, item: TokenStream) -> TokenStream {
543 let args = parse_macro_input!(attr as RouteAttr);
546 let fn_item = parse_macro_input!(item as ItemFn);
547
548 use crate::route::core::route_handler_no_register;
550 route_handler_no_register(args, fn_item)
551}
552
553#[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#[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 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 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 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}