use crate::extractor::body::deal_with_body_attr;
use crate::extractor::path::deal_with_path_attr;
use crate::route::layer::extract_layer_attrs;
use crate::route::{RouteAttr, build_register_expr};
use crate::toolkit::exactors::build_struct_from_query;
use crate::toolkit::rout_arg::{
FnArgResult, IntoFnArgs, RouteFnArg, build_config_value_injector, build_dep_extractors,
};
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
#[cfg(all(feature = "utoipa", feature = "auto"))]
use quote::format_ident;
use quote::quote;
use syn::{ItemFn, Stmt, parse_quote};
#[cfg(feature = "utoipa")]
use crate::utoipa::{
attributes::parse_utoipa_attrs,
generator::{HttpMethod, generate_utoipa_path_attr},
infer::infer_openapi_config,
};
pub fn route_handler(args: RouteAttr, mut fn_item: ItemFn) -> TokenStream {
let fn_name = fn_item.sig.ident.clone();
let layer_attrs = extract_layer_attrs(&fn_item.attrs);
fn_item.attrs.retain(|attr| !attr.path().is_ident("layer"));
#[cfg(feature = "utoipa")]
let original_attrs = fn_item.attrs.clone();
#[cfg(feature = "utoipa")]
let original_inputs = fn_item.sig.inputs.clone();
#[cfg(feature = "utoipa")]
let original_output = fn_item.sig.output.clone();
let sig = &mut fn_item.sig;
if matches!(sig.output, syn::ReturnType::Default) {
sig.output = parse_quote!(-> impl ::miko::http::response::into_response::IntoResponse)
}
let inject_segs: Vec<Stmt> = Vec::new();
let rfa = RouteFnArg::from_punctuated(&mut sig.inputs);
let path_inputs = rfa.gen_fn_args(deal_with_path_attr);
let body_inputs = rfa.gen_fn_args(deal_with_body_attr);
let plain_inputs = rfa.gen_fn_args(|rfa| {
if rfa.mark.is_empty() {
FnArgResult::Keep
} else {
FnArgResult::Remove
}
});
let (dep_inputs, dep_stmts) = build_dep_extractors(&rfa);
let mut config_value_stmts = Vec::new();
build_config_value_injector(&rfa, &mut config_value_stmts);
sig.inputs.clear();
sig.inputs.extend(path_inputs);
let q_struct_ident = Ident::new(&format!("__{}_QueryStruct", fn_name), Span::call_site());
let (q_struct, q_struct_exactor) = build_struct_from_query(&rfa, q_struct_ident);
if q_struct.is_some() {
sig.inputs.push(q_struct_exactor.unwrap());
}
sig.inputs.extend(dep_inputs);
sig.inputs.extend(plain_inputs);
sig.inputs.extend(body_inputs);
let user_stmts = &fn_item.block.stmts.clone();
let inventory_collect: Option<proc_macro2::TokenStream> = if cfg!(feature = "auto") {
Some(build_register_expr(&args, &fn_name.clone(), &layer_attrs))
} else {
None
};
#[cfg(feature = "utoipa")]
let utoipa_attr =
generate_utoipa_attr(&args, &original_attrs, &original_inputs, &original_output);
#[cfg(all(feature = "utoipa", feature = "auto"))]
let openapi_collect = {
let openapi_ident = format_ident!("__miko_openapi_{}", fn_name);
quote! {
#[allow(non_camel_case_types)]
#[derive(::miko::OpenApi)]
#[openapi(paths(#fn_name))]
struct #openapi_ident;
::miko::inventory::submit! {
::miko::openapi::OpenApiRoute {
openapi: <#openapi_ident as ::miko::utoipa::OpenApi>::openapi,
module_path: module_path!(),
}
}
}
};
#[cfg(all(feature = "utoipa", not(feature = "auto")))]
let openapi_collect = proc_macro2::TokenStream::new();
#[cfg(feature = "utoipa")]
{
quote! {
#q_struct
#utoipa_attr
#sig {
#(#inject_segs)*
#(#dep_stmts)*
#(#config_value_stmts)*
#(#user_stmts)*
}
#inventory_collect
#openapi_collect
}
.into()
}
#[cfg(not(feature = "utoipa"))]
{
quote! {
#q_struct
#sig {
#(#inject_segs)*
#(#dep_stmts)*
#(#config_value_stmts)*
#(#user_stmts)*
}
#inventory_collect
}
.into()
}
}
#[cfg(feature = "utoipa")]
pub fn route_handler_no_register(args: RouteAttr, mut fn_item: ItemFn) -> TokenStream {
let fn_name = fn_item.sig.ident.clone();
let _layer_attrs = extract_layer_attrs(&fn_item.attrs);
fn_item.attrs.retain(|attr| !attr.path().is_ident("layer"));
let original_attrs = fn_item.attrs.clone();
let original_inputs = fn_item.sig.inputs.clone();
let original_output = fn_item.sig.output.clone();
let sig = &mut fn_item.sig;
if matches!(sig.output, syn::ReturnType::Default) {
sig.output = parse_quote!(-> impl ::miko::http::response::into_response::IntoResponse)
}
let inject_segs: Vec<Stmt> = Vec::new();
let rfa = RouteFnArg::from_punctuated(&mut sig.inputs);
let path_inputs = rfa.gen_fn_args(deal_with_path_attr);
let body_inputs = rfa.gen_fn_args(deal_with_body_attr);
let plain_inputs = rfa.gen_fn_args(|rfa| {
if rfa.mark.is_empty() {
FnArgResult::Keep
} else {
FnArgResult::Remove
}
});
let (dep_inputs, dep_stmts) = build_dep_extractors(&rfa);
let mut config_value_stmts = Vec::new();
build_config_value_injector(&rfa, &mut config_value_stmts);
sig.inputs.clear();
sig.inputs.extend(path_inputs);
let q_struct_ident = Ident::new(&format!("__{}_QueryStruct", fn_name), Span::call_site());
let (q_struct, q_struct_exactor) = build_struct_from_query(&rfa, q_struct_ident);
if q_struct.is_some() {
sig.inputs.push(q_struct_exactor.unwrap());
}
sig.inputs.extend(dep_inputs);
sig.inputs.extend(plain_inputs);
sig.inputs.extend(body_inputs);
let user_stmts = &fn_item.block.stmts.clone();
let utoipa_attr =
generate_utoipa_attr(&args, &original_attrs, &original_inputs, &original_output);
quote! {
#q_struct
#utoipa_attr
#sig {
#(#inject_segs)*
#(#dep_stmts)*
#(#config_value_stmts)*
#(#user_stmts)*
}
}
.into()
}
#[cfg(feature = "utoipa")]
fn generate_utoipa_attr(
args: &RouteAttr,
original_attrs: &[syn::Attribute],
original_inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
original_output: &syn::ReturnType,
) -> proc_macro2::TokenStream {
let mut user_config = parse_utoipa_attrs(original_attrs);
let inferred = infer_openapi_config(original_attrs, original_inputs, original_output);
user_config.auto_summary = inferred.auto_summary;
user_config.auto_description = inferred.auto_description;
user_config.auto_params = inferred.auto_params;
user_config.auto_response = inferred.auto_response;
user_config.auto_request_body = inferred.auto_request_body;
let method = if let Some(ref methods) = args.method {
if let Some(first_method) = methods.first() {
HttpMethod::from_hyper_method(first_method)
} else {
HttpMethod::Get
}
} else {
HttpMethod::Get
};
generate_utoipa_path_attr(&method, &args.path, &user_config)
}