use crate::route::RouteAttr;
use crate::route::core::route_handler;
use crate::toolkit::attr::StrAttrMap;
#[cfg(feature = "auto")]
use crate::toolkit::impl_operation::{get_constructor, inject_deps};
use crate::toolkit::rout_arg::{
FnArgResult, IntoFnArgs, RouteFnArg, build_clone_stmt, build_config_value_injector,
build_dep_injector,
};
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{ItemFn, ItemMod, parse_macro_input};
mod extractor;
mod mod_transform;
mod route;
mod toolkit;
#[cfg(feature = "utoipa")]
mod utoipa;
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as RouteAttr);
let fn_item = parse_macro_input!(item as ItemFn);
route_handler(args, fn_item)
}
#[proc_macro_attribute]
pub fn miko(attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as ItemFn);
let fn_name = &input_fn.sig.ident;
let fn_vis = &input_fn.vis;
let str_attr_map = parse_macro_input!(attr as StrAttrMap);
let user_statements = &input_fn.block.stmts;
let set_panic_hook = if str_attr_map.map.contains_key("sse") {
Some(quote! {
::miko::http::response::sse::set_sse_panic_hook();
})
} else {
None
};
let catch_panic = if str_attr_map.map.contains_key("catch") {
if cfg!(feature = "catch_panic") {
Some(quote! {
router.with_catch_panic();
})
} else {
return quote! {
compile_error!("`catch` attribute requires `catch_panic` feature to be enabled");
}
.into();
}
} else {
None
};
let build_sign = str_attr_map.map.contains_key("build");
if build_sign {
quote! {
#fn_vis async fn #fn_name() -> ::miko::app::Application {
#set_panic_hook
let mut _config = ::miko::app::config::ServerSettings::from_global_settings();
let mut router = ::miko::router::Router::new();
#catch_panic
#( #user_statements )*
router.merge(::miko::auto::collect_global_router());
::miko::app::Application::new(_config, router.take())
}
}
} else {
quote! {
#[::miko::tokio::main]
async fn main() {
#set_panic_hook
let mut _config = ::miko::app::config::ServerSettings::from_global_settings();
let mut router = ::miko::router::Router::new();
#catch_panic
#( #user_statements )*
router.merge(::miko::auto::collect_global_router());
let app = ::miko::app::Application::new(_config, router.take());
app.run().await.unwrap();
}
}
}
.into()
}
macro_rules! derive_route_macro {
($macro_name: ident, $method_ident:ident) => {
#[doc = concat!("简写:等价于 `#[route(..., method = \"", stringify!($method_ident), "\" )]`。\n\n",
"仅当启用 `auto` feature 且应用通过 `#[miko]` 启动时,框架才会自动注册由该宏生成的路由;\n",
"否则该宏仅生成处理函数,路由需在初始化代码中手动注册。")]
#[proc_macro_attribute]
pub fn $macro_name(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut args = syn::parse_macro_input!(attr as RouteAttr);
let fn_item = syn::parse_macro_input!(item as ItemFn);
let method_to_add = ::hyper::Method::$method_ident;
match &mut args.method {
Some(existing_methods) => {
existing_methods.push(method_to_add);
}
None => {
args.method = Some(vec![method_to_add]);
}
}
route_handler(args, fn_item)
}
};
}
derive_route_macro!(get, GET);
derive_route_macro!(post, POST);
derive_route_macro!(put, PUT);
derive_route_macro!(delete, DELETE);
derive_route_macro!(patch, PATCH);
derive_route_macro!(head, HEAD);
derive_route_macro!(options, OPTIONS);
derive_route_macro!(trace, TRACE);
derive_route_macro!(connect, CONNECT);
#[cfg(feature = "auto")]
#[proc_macro_attribute]
pub fn component(attr: TokenStream, input: TokenStream) -> TokenStream {
use quote::format_ident;
use syn::{ItemImpl, TypePath};
let args = syn::parse_macro_input!(attr as StrAttrMap);
let input_struct = parse_macro_input!(input as ItemImpl);
let prewarm = args.get("prewarm").is_some();
let mut lifetime = "singleton".to_string();
let mut lifetime_specified = false;
let mut set_lifetime = |mode: &str| {
let normalized = mode.to_ascii_lowercase();
match normalized.as_str() {
"singleton" | "request" | "transient" => {
if lifetime_specified && lifetime != normalized {
panic!(
"Conflicting #[component] lifetime: both '{}' and '{}' specified",
lifetime, normalized
);
}
lifetime = normalized;
lifetime_specified = true;
}
_ => panic!(
"Invalid #[component] lifetime '{}'. Expected `singleton`, `request`, or `transient`.",
mode
),
}
};
if let Some(mode) = args.get("mode") {
set_lifetime(mode);
} else if let Some(default_mode) = args.default.as_ref() {
set_lifetime(default_mode);
}
if args.map.contains_key("singleton") {
set_lifetime("singleton");
}
if args.map.contains_key("transient") {
set_lifetime("transient");
}
if args.map.contains_key("request") {
set_lifetime("request");
}
if prewarm && lifetime != "singleton" {
panic!("`#[component(prewarm)]` is only valid for singleton components");
}
let lifetime_tokens = match lifetime.as_str() {
"singleton" => quote!(::miko::dependency_container::DependencyLifetime::Singleton),
"request" => quote!(::miko::dependency_container::DependencyLifetime::Request),
"transient" => quote!(::miko::dependency_container::DependencyLifetime::Transient),
_ => unreachable!(),
};
let mut depend_get_stmts = Vec::new();
let mut arg_idents = Vec::new();
let type_ident = match *input_struct.self_ty.clone() {
syn::Type::Path(TypePath { path, .. }) => path
.segments
.last()
.map(|seg| seg.ident.clone())
.unwrap_or_else(|| format_ident!("UnknowType")),
_ => format_ident!("UnknowType"),
};
if let Some(method) = get_constructor(&input_struct.items) {
if method.sig.asyncness.is_none() {
panic!("service method new must be async")
}
let args = &method.sig.inputs;
inject_deps(args, &mut depend_get_stmts, &mut arg_idents);
}
quote! {
#input_struct
::miko::inventory::submit! {
::miko::dependency_container::DependencyDefFn(|| {
::miko::dependency_container::DependencyDef {
type_id: std::any::TypeId::of::<#type_ident>(),
type_name: std::any::type_name::<#type_ident>(),
prewarm: #prewarm,
name: "___",
lifetime: #lifetime_tokens,
init_fn: |__resolve_context| {
Box::pin(async move {
#(#depend_get_stmts)*
let val: #type_ident = #type_ident::new(#(#arg_idents),*).await;
Ok(
::std::boxed::Box::new(val)
as ::std::boxed::Box<dyn ::std::any::Any + Send + Sync>
)
})
}
}
})
}
}
.into()
}
#[cfg(feature = "utoipa")]
#[proc_macro_attribute]
pub fn u_response(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[cfg(feature = "utoipa")]
#[proc_macro_attribute]
pub fn u_tag(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[cfg(feature = "utoipa")]
#[proc_macro_attribute]
pub fn u_summary(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[cfg(feature = "utoipa")]
#[proc_macro_attribute]
pub fn u_description(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[cfg(feature = "utoipa")]
#[proc_macro_attribute]
pub fn u_request_body(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[cfg(feature = "utoipa")]
#[proc_macro_attribute]
pub fn u_param(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[cfg(feature = "utoipa")]
#[proc_macro_attribute]
pub fn u_deprecated(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[proc_macro_attribute]
pub fn desc(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[proc_macro_attribute]
pub fn query(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[proc_macro_attribute]
pub fn body(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[proc_macro_attribute]
pub fn layer(attr: TokenStream, item: TokenStream) -> TokenStream {
if let Ok(mut mod_item) = syn::parse::<ItemMod>(item.clone()) {
let layer_attr = parse_macro_input!(attr as mod_transform::ModLayerAttr);
mod_transform::apply_transform_to_module(
&mut mod_item,
mod_transform::TransformOp::Layer(layer_attr.expr),
);
return quote! { #mod_item }.into();
}
item
}
#[cfg(feature = "utoipa")]
#[proc_macro_attribute]
pub fn miko_path(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as RouteAttr);
let fn_item = parse_macro_input!(item as ItemFn);
use crate::route::core::route_handler_no_register;
route_handler_no_register(args, fn_item)
}
#[proc_macro_attribute]
pub fn prefix(attr: TokenStream, item: TokenStream) -> TokenStream {
let prefix_attr = parse_macro_input!(attr as mod_transform::PrefixAttr);
let mut mod_item = parse_macro_input!(item as ItemMod);
mod_transform::apply_transform_to_module(
&mut mod_item,
mod_transform::TransformOp::Prefix(prefix_attr.path),
);
quote! { #mod_item }.into()
}
#[proc_macro_attribute]
pub fn middleware(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut input_fn = parse_macro_input!(item as ItemFn);
let fn_name = &input_fn.sig.ident;
let vis = &input_fn.vis;
let block = &input_fn.block;
let attrs = &input_fn.attrs;
let args = RouteFnArg::from_punctuated(&mut input_fn.sig.inputs);
let mut req_ident = format_ident!("_req");
let mut next_ident = format_ident!("_next");
let mut config_stmts = Vec::new();
let mut deps_stmts = Vec::new();
let mut clone_stmts = Vec::new();
let outer_args = args.gen_fn_args(|rfa| {
if let syn::Type::Path(path) = &rfa.ty {
if path.path.segments.last().unwrap().ident == "Req" {
req_ident = rfa.ident.clone();
return FnArgResult::Remove;
} else if path.path.segments.last().unwrap().ident == "Next" {
next_ident = rfa.ident.clone();
return FnArgResult::Remove;
}
}
if !rfa.mark.is_empty() {
if rfa.marked_by("config") || rfa.marked_by("dep") {
return FnArgResult::Remove;
} else {
panic!("middleware only support mark #[config] or #[dep]");
}
}
build_clone_stmt(rfa, &mut clone_stmts);
FnArgResult::Keep
});
build_dep_injector(&args, &req_ident, &mut deps_stmts);
build_config_value_injector(&args, &mut config_stmts);
let mut inputs = input_fn.sig.inputs;
inputs.clear();
inputs.extend(outer_args);
quote! {
#(#attrs)*
#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> {
::miko::middleware::middleware_from_fn(move |#req_ident: ::miko::miko_core::Req, #next_ident: ::miko::middleware::Next| {
#( #clone_stmts )*
Box::pin(async move {
#( #deps_stmts )*
#( #config_stmts )*
#block
}) as ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ::miko::AppResult<::miko::miko_core::Resp>> + Send>>
})
}
}.into()
}