use crate::crate_paths::{get_reinhardt_crate, get_reinhardt_di_crate};
use crate::injectable_common::generate_inject_resolver_expr;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{FnArg, ItemFn, Pat, PatType, Result};
fn is_inject_attr(attr: &syn::Attribute) -> bool {
attr.path().is_ident("inject")
}
pub(crate) fn routes_impl(args: TokenStream, input: ItemFn) -> Result<TokenStream> {
if !args.is_empty() {
return Err(syn::Error::new_spanned(
args,
"The #[routes] macro no longer accepts arguments. \
The flags `standalone`, `client_inventory`, `server_only`, \
`no_client_resolvers`, and `no_ws_resolvers` have been removed \
as part of the URL routing simplification (Issue #4784).",
));
}
let reinhardt = get_reinhardt_crate();
let fn_name = &input.sig.ident;
let fn_vis = &input.vis;
let fn_attrs = &input.attrs;
let fn_block = &input.block;
if matches!(input.sig.output, syn::ReturnType::Default) {
return Err(syn::Error::new_spanned(
&input.sig,
"#[routes] function must have a return type (-> UnifiedRouter)",
));
}
let is_async = input.sig.asyncness.is_some();
let mut inject_params = Vec::new();
let mut has_inject = false;
for arg in &input.sig.inputs {
if let FnArg::Typed(PatType { attrs, pat, ty, .. }) = arg
&& attrs.iter().any(is_inject_attr)
{
has_inject = true;
inject_params.push((pat.clone(), ty.clone()));
}
}
if !is_async && has_inject {
return Err(syn::Error::new_spanned(
&input.sig,
"Sync #[routes] functions cannot use #[inject] parameters. \
Make the function async to use dependency injection.",
));
}
let native_only = quote! {
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
};
let wasm_only = quote! {
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
};
let reinhardt_urls = crate::crate_paths::get_reinhardt_urls_crate();
let expanded = if !is_async {
let fn_sig = &input.sig;
quote! {
#[allow(private_interfaces)]
#(#fn_attrs)*
#fn_vis #fn_sig #fn_block
#native_only
#[allow(unsafe_attr_outside_unsafe)]
const _: () = {
fn __get_server_router() -> ::std::sync::Arc<#reinhardt::ServerRouter> {
let unified = #fn_name();
::std::sync::Arc::new(unified.into_server())
}
#reinhardt::inventory::submit! {
#reinhardt::UrlPatternsRegistration::__macro_new(__get_server_router)
}
};
#native_only
#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(non_upper_case_globals, dead_code)]
static __reinhardt_routes_registration_marker: () = ();
#wasm_only
#[allow(unsafe_attr_outside_unsafe)]
const _: () = {
fn __get_client_router() -> ::std::sync::Arc<#reinhardt_urls::routers::ClientRouter> {
let unified = #fn_name();
::std::sync::Arc::new(unified.into_client())
}
#reinhardt_urls::inventory::submit! {
#reinhardt_urls::routers::ClientRouterRegistration::__macro_new(__get_client_router)
}
};
}
} else if !has_inject {
let fn_sig = &input.sig;
quote! {
#native_only
#[allow(private_interfaces)]
#(#fn_attrs)*
#fn_vis #fn_sig #fn_block
#native_only
#[allow(unsafe_attr_outside_unsafe)]
const _: () = {
fn __get_server_router() -> ::std::pin::Pin<
::std::boxed::Box<
dyn ::std::future::Future<
Output = ::std::result::Result<
::std::sync::Arc<#reinhardt::ServerRouter>,
::std::boxed::Box<dyn ::std::error::Error + Send + Sync>,
>,
> + Send,
>,
> {
::std::boxed::Box::pin(async {
let unified = #fn_name().await;
::std::result::Result::Ok(::std::sync::Arc::new(unified.into_server()))
})
}
#reinhardt::inventory::submit! {
#reinhardt::UrlPatternsRegistration::__macro_new_async(__get_server_router)
}
};
#native_only
#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(non_upper_case_globals, dead_code)]
static __reinhardt_routes_registration_marker: () = ();
}
} else {
let di_crate = get_reinhardt_di_crate();
let inject_resolutions: Vec<_> = inject_params
.iter()
.map(|(pat, ty)| {
let resolve_expr =
generate_inject_resolver_expr(&di_crate, ty, quote! { &*__ctx }, true);
quote! {
let #pat: #ty = #resolve_expr
.map_err(|e| -> ::std::boxed::Box<dyn ::std::error::Error + Send + Sync> {
::std::boxed::Box::new(e)
})?;
}
})
.collect();
let inject_param_names: Vec<_> = inject_params
.iter()
.map(|(pat, _)| {
if let Pat::Ident(pat_ident) = pat.as_ref() {
let ident = &pat_ident.ident;
quote! { #ident }
} else {
quote! { #pat }
}
})
.collect();
let fn_return = &input.sig.output;
let fn_generics = &input.sig.generics;
let stripped_params: Vec<_> = input
.sig
.inputs
.iter()
.map(|arg| {
if let FnArg::Typed(pat_type) = arg {
let attrs: Vec<_> = pat_type
.attrs
.iter()
.filter(|a| !is_inject_attr(a))
.collect();
let pat = &pat_type.pat;
let ty = &pat_type.ty;
quote! { #(#attrs)* #pat: #ty }
} else {
quote! { #arg }
}
})
.collect();
quote! {
#native_only
#[allow(private_interfaces)]
#(#fn_attrs)*
#fn_vis async fn #fn_name #fn_generics(#(#stripped_params),*) #fn_return #fn_block
#native_only
#[allow(unsafe_attr_outside_unsafe)]
const _: () = {
fn __get_server_router() -> ::std::pin::Pin<
::std::boxed::Box<
dyn ::std::future::Future<
Output = ::std::result::Result<
::std::sync::Arc<#reinhardt::ServerRouter>,
::std::boxed::Box<dyn ::std::error::Error + Send + Sync>,
>,
> + Send,
>,
> {
::std::boxed::Box::pin(async {
let __scope = ::std::sync::Arc::new(
#di_crate::SingletonScope::new()
);
let __ctx = ::std::sync::Arc::new(
#di_crate::InjectionContext::builder(__scope).build()
);
#(#inject_resolutions)*
let unified = #fn_name(#(#inject_param_names),*).await;
::std::result::Result::Ok(::std::sync::Arc::new(unified.into_server()))
})
}
#reinhardt::inventory::submit! {
#reinhardt::UrlPatternsRegistration::__macro_new_async(__get_server_router)
}
};
#native_only
#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(non_upper_case_globals, dead_code)]
static __reinhardt_routes_registration_marker: () = ();
}
};
Ok(expanded)
}