use crate::crate_paths::{
get_reinhardt_core_crate, get_reinhardt_di_crate, get_reinhardt_signals_crate,
};
use syn::{Expr, Token, punctuated::Punctuated};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum InjectionScope {
#[default]
Request,
Singleton,
}
#[derive(Debug, Clone)]
pub(crate) struct InjectOptions {
pub use_cache: bool,
pub scope: InjectionScope,
}
impl Default for InjectOptions {
fn default() -> Self {
Self {
use_cache: true,
scope: InjectionScope::Request,
}
}
}
#[derive(Debug, Clone)]
pub(crate) enum DefaultValue {
DefaultTrait,
Expression(Expr),
None,
}
#[derive(Debug, Clone)]
pub(crate) struct NoInjectOptions {
pub default: DefaultValue,
}
pub(crate) fn is_inject_attr(attr: &syn::Attribute) -> bool {
attr.path().is_ident("inject")
}
pub(crate) fn is_no_inject_attr(attr: &syn::Attribute) -> bool {
attr.path().is_ident("no_inject")
}
pub(crate) fn parse_inject_options(attrs: &[syn::Attribute]) -> InjectOptions {
let mut options = InjectOptions::default();
for attr in attrs {
if !is_inject_attr(attr) {
continue;
}
if let syn::Meta::List(meta_list) = &attr.meta
&& let Ok(nested) =
meta_list.parse_args_with(Punctuated::<syn::Meta, Token![,]>::parse_terminated)
{
for meta in nested {
if let syn::Meta::NameValue(nv) = meta {
if nv.path.is_ident("cache") {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Bool(lit_bool),
..
}) = &nv.value
{
options.use_cache = lit_bool.value;
}
} else if nv.path.is_ident("scope")
&& let syn::Expr::Path(path_expr) = &nv.value
{
if path_expr.path.is_ident("Singleton") {
options.scope = InjectionScope::Singleton;
} else if path_expr.path.is_ident("Request") {
options.scope = InjectionScope::Request;
}
}
}
}
}
}
options
}
pub(crate) fn parse_no_inject_options(attrs: &[syn::Attribute]) -> Option<NoInjectOptions> {
for attr in attrs {
if !is_no_inject_attr(attr) {
continue;
}
if matches!(&attr.meta, syn::Meta::Path(_)) {
return Some(NoInjectOptions {
default: DefaultValue::None,
});
}
if let syn::Meta::List(meta_list) = &attr.meta
&& let Ok(nested) =
meta_list.parse_args_with(Punctuated::<syn::Meta, Token![,]>::parse_terminated)
{
for meta in nested {
if let syn::Meta::NameValue(nv) = meta
&& nv.path.is_ident("default")
{
if let Expr::Path(path_expr) = &nv.value
&& path_expr.path.is_ident("Default")
{
return Some(NoInjectOptions {
default: DefaultValue::DefaultTrait,
});
} else {
return Some(NoInjectOptions {
default: DefaultValue::Expression(nv.value.clone()),
});
}
}
}
}
return Some(NoInjectOptions {
default: DefaultValue::None,
});
}
None
}
use proc_macro2::TokenStream;
#[derive(Clone)]
pub(crate) struct InjectParamInfo {
pub pat: Box<syn::Pat>,
pub ty: Box<syn::Type>,
pub options: InjectOptions,
}
pub(crate) fn detect_inject_params(
inputs: &syn::punctuated::Punctuated<syn::FnArg, Token![,]>,
) -> Vec<InjectParamInfo> {
let mut inject_params = Vec::new();
for input in inputs {
if let syn::FnArg::Typed(syn::PatType { attrs, pat, ty, .. }) = input {
let has_inject = attrs.iter().any(is_inject_attr);
if has_inject {
let options = parse_inject_options(attrs);
inject_params.push(InjectParamInfo {
pat: pat.clone(),
ty: ty.clone(),
options,
});
}
}
}
inject_params
}
pub(crate) fn generate_di_context_extraction(request_ident: &syn::Ident) -> TokenStream {
let di_crate = get_reinhardt_di_crate();
let core_crate = get_reinhardt_core_crate();
quote::quote! {
let __shared_ctx = #request_ident.get_di_context::<::std::sync::Arc<#di_crate::InjectionContext>>()
.ok_or_else(|| #core_crate::exception::Error::Internal(
"DI context not set. Ensure the router is configured with .with_di_context()".to_string()
))?;
let __di_request = #request_ident.clone_for_di();
let __di_ctx = ::std::sync::Arc::new((*__shared_ctx).fork_for_request(__di_request));
let __resolve_ctx = #di_crate::resolve_context::ResolveContext {
root: ::std::sync::Arc::clone(&__shared_ctx),
current: ::std::sync::Arc::clone(&__di_ctx),
};
}
}
pub(crate) fn generate_di_context_extraction_from_option(ctx_ident: &syn::Ident) -> TokenStream {
let signals_crate = get_reinhardt_signals_crate();
quote::quote! {
let __di_ctx = #ctx_ident
.as_ref()
.ok_or_else(|| #signals_crate::SignalError::new(
"DI context not available. Use signal.send_with_di_context() to enable injection"
))?;
}
}
pub(crate) fn generate_injection_calls(inject_params: &[InjectParamInfo]) -> Vec<TokenStream> {
let di_crate = get_reinhardt_di_crate();
let core_crate = get_reinhardt_core_crate();
inject_params
.iter()
.map(|param| {
let pat = ¶m.pat;
let ty = ¶m.ty;
let use_cache = param.options.use_cache;
if use_cache {
quote::quote! {
let #pat: #ty = #di_crate::Depends::<#ty>::resolve(&__di_ctx, true)
.await
.map_err(|e| #core_crate::exception::Error::Internal(
format!("Dependency injection failed for {}: {:?}", stringify!(#ty), e)
))?
.into_inner();
}
} else {
quote::quote! {
let #pat: #ty = #di_crate::Depends::<#ty>::resolve(&__di_ctx, false)
.await
.map_err(|e| #core_crate::exception::Error::Internal(
format!("Dependency injection failed for {}: {:?}", stringify!(#ty), e)
))?
.into_inner();
}
}
})
.collect()
}
pub(crate) fn generate_injection_calls_with_error<F>(
inject_params: &[InjectParamInfo],
error_mapper: F,
) -> Vec<TokenStream>
where
F: Fn(&syn::Type) -> TokenStream,
{
let di_crate = get_reinhardt_di_crate();
inject_params
.iter()
.map(|param| {
let pat = ¶m.pat;
let ty = ¶m.ty;
let use_cache = param.options.use_cache;
let error_conversion = error_mapper(ty);
if use_cache {
quote::quote! {
let #pat: #ty = #di_crate::Depends::<#ty>::resolve(&__di_ctx, true)
.await
.map_err(|e| #error_conversion)?
.into_inner();
}
} else {
quote::quote! {
let #pat: #ty = #di_crate::Depends::<#ty>::resolve(&__di_ctx, false)
.await
.map_err(|e| #error_conversion)?
.into_inner();
}
}
})
.collect()
}
pub(crate) fn strip_inject_attrs(
inputs: &syn::punctuated::Punctuated<syn::FnArg, Token![,]>,
) -> Vec<syn::FnArg> {
inputs
.iter()
.map(|arg| {
if let syn::FnArg::Typed(pat_type) = arg {
let mut pat_type = pat_type.clone();
pat_type.attrs.retain(|attr| !is_inject_attr(attr));
syn::FnArg::Typed(pat_type)
} else {
arg.clone()
}
})
.collect()
}