use crate::function_handle::FunctionHandle;
use crate::override_registry::OverrideRegistry;
use crate::scope::{RequestScope, SingletonScope};
use reinhardt_http::Request as HttpRequest;
use std::any::Any;
use std::sync::Arc;
#[cfg(feature = "params")]
pub use crate::params::{ParamContext, Request};
pub struct InjectionContext {
request_scope: RequestScope,
singleton_scope: Arc<SingletonScope>,
override_registry: Arc<OverrideRegistry>,
registry: Option<Arc<crate::registry::DependencyRegistry>>,
#[cfg(feature = "params")]
request: Option<Arc<Request>>,
#[cfg(feature = "params")]
param_context: Option<Arc<ParamContext>>,
}
pub struct InjectionContextBuilder {
singleton_scope: Arc<SingletonScope>,
registry: Option<Arc<crate::registry::DependencyRegistry>>,
#[cfg(feature = "params")]
request: Option<Request>,
#[cfg(feature = "params")]
param_context: Option<ParamContext>,
}
impl InjectionContextBuilder {
#[cfg(feature = "params")]
pub fn with_request(mut self, request: Request) -> Self {
self.request = Some(request);
self
}
#[cfg(feature = "params")]
pub fn with_param_context(mut self, param_context: ParamContext) -> Self {
self.param_context = Some(param_context);
self
}
pub fn with_registry(mut self, registry: Arc<crate::registry::DependencyRegistry>) -> Self {
self.registry = Some(registry);
self
}
pub fn singleton<T: std::any::Any + Send + Sync>(self, instance: T) -> Self {
self.singleton_scope.set(instance);
self
}
pub fn build(self) -> InjectionContext {
InjectionContext {
request_scope: RequestScope::new(),
singleton_scope: self.singleton_scope,
override_registry: Arc::new(OverrideRegistry::new()),
registry: self.registry,
#[cfg(feature = "params")]
request: self.request.map(Arc::new),
#[cfg(feature = "params")]
param_context: self.param_context.map(Arc::new),
}
}
}
impl Clone for InjectionContext {
fn clone(&self) -> Self {
Self {
request_scope: self.request_scope.deep_clone(),
singleton_scope: Arc::clone(&self.singleton_scope),
override_registry: Arc::clone(&self.override_registry),
registry: self.registry.clone(),
#[cfg(feature = "params")]
request: self.request.clone(),
#[cfg(feature = "params")]
param_context: self.param_context.clone(),
}
}
}
impl InjectionContext {
pub fn builder(singleton_scope: impl Into<Arc<SingletonScope>>) -> InjectionContextBuilder {
InjectionContextBuilder {
singleton_scope: singleton_scope.into(),
registry: None,
#[cfg(feature = "params")]
request: None,
#[cfg(feature = "params")]
param_context: None,
}
}
#[cfg(feature = "params")]
pub fn get_http_request(&self) -> Option<&Request> {
self.request.as_ref().map(|arc| arc.as_ref())
}
#[cfg(feature = "params")]
pub fn get_param_context(&self) -> Option<&ParamContext> {
self.param_context.as_ref().map(|arc| arc.as_ref())
}
#[cfg(feature = "params")]
pub fn set_http_request(&mut self, request: Request, param_context: ParamContext) {
self.request = Some(Arc::new(request));
self.param_context = Some(Arc::new(param_context));
}
pub fn get_request<T: Any + Send + Sync>(&self) -> Option<Arc<T>> {
self.request_scope.get::<T>()
}
pub fn set_request<T: Any + Send + Sync>(&self, value: T) {
self.request_scope.set(value);
}
pub(crate) fn set_request_arc<T: Any + Send + Sync>(&self, value: Arc<T>) {
self.request_scope.set_arc(value);
}
pub fn get_singleton<T: Any + Send + Sync>(&self) -> Option<Arc<T>> {
self.singleton_scope.get::<T>()
}
pub fn set_singleton<T: Any + Send + Sync>(&self, value: T) {
self.singleton_scope.set(value);
}
fn set_singleton_arc<T: Any + Send + Sync>(&self, value: Arc<T>) {
self.singleton_scope.set_arc(value);
}
pub fn singleton_scope(&self) -> &Arc<SingletonScope> {
&self.singleton_scope
}
fn fork_inner(
&self,
#[cfg(feature = "params")] request: Option<Arc<HttpRequest>>,
#[cfg(feature = "params")] param_context: Option<Arc<ParamContext>>,
) -> InjectionContext {
InjectionContext {
request_scope: RequestScope::new(),
singleton_scope: self.singleton_scope.clone(),
override_registry: self.override_registry.clone(),
registry: self.registry.clone(),
#[cfg(feature = "params")]
request,
#[cfg(feature = "params")]
param_context,
}
}
pub fn fork_for_request(&self, request: HttpRequest) -> InjectionContext {
let request_arc = Arc::new(request);
#[cfg(feature = "params")]
let param_context = Some(Arc::new(ParamContext::with_path_params(
request_arc.path_params.clone(),
)));
let ctx = self.fork_inner(
#[cfg(feature = "params")]
Some(Arc::clone(&request_arc)),
#[cfg(feature = "params")]
param_context,
);
ctx.set_request_arc(request_arc);
ctx
}
pub fn fork(&self) -> InjectionContext {
self.fork_inner(
#[cfg(feature = "params")]
None,
#[cfg(feature = "params")]
None,
)
}
pub fn overrides(&self) -> &OverrideRegistry {
&self.override_registry
}
pub fn dependency<O>(&self, func: fn() -> O) -> FunctionHandle<'_, O>
where
O: Clone + Send + Sync + 'static,
{
let func_ptr = func as usize;
FunctionHandle::new(self, func_ptr)
}
pub fn get_override<O: Clone + 'static>(&self, func_ptr: usize) -> Option<O> {
self.override_registry.get(func_ptr)
}
pub fn clear_overrides(&self) {
self.override_registry.clear();
}
fn find_type_registration<T: Any + Send + Sync + 'static>(
&self,
) -> Option<(
crate::registry::DependencyScope,
&Arc<crate::registry::DependencyRegistry>,
)> {
use crate::registry::global_registry;
if let Some(ref reg) = self.registry
&& let Some(scope) = reg.get_scope::<T>()
{
return Some((scope, reg));
}
let global = global_registry();
global.get_scope::<T>().map(|scope| (scope, global))
}
pub async fn resolve<T: Any + Send + Sync + 'static>(&self) -> crate::DiResult<Arc<T>> {
use crate::cycle_detection::{
begin_scoped_resolution, current_dependent_scope, current_dependent_type_name,
register_type_name, with_cycle_detection_scope,
};
use crate::registry::DependencyScope;
with_cycle_detection_scope(async {
let type_id = std::any::TypeId::of::<T>();
let type_name = std::any::type_name::<T>();
register_type_name::<T>(type_name);
let (scope, registry) = match self.find_type_registration::<T>() {
Some(entry) => entry,
None => {
if let Some(cached) = self.get_singleton::<T>() {
return Ok(cached);
}
if let Some(cached) = self.get_request::<T>() {
if !DependencyScope::Request.outlives(current_dependent_scope()) {
return Err(crate::DiError::ScopeError(format!(
"Scope violation: {:?}-scoped '{}' cannot resolve \
Request-scoped '{}' (pre-seeded); the dependency would \
be captured with a shorter lifetime",
current_dependent_scope(),
current_dependent_type_name(),
type_name,
)));
}
return Ok(cached);
}
return Err(crate::DiError::DependencyNotRegistered {
type_name: type_name.to_string(),
});
}
};
if !scope.outlives(current_dependent_scope()) {
return Err(crate::DiError::ScopeError(format!(
"Scope violation: {:?}-scoped '{}' cannot resolve \
{:?}-scoped '{}'; the dependency would be captured with \
a shorter lifetime",
current_dependent_scope(),
current_dependent_type_name(),
scope,
type_name,
)));
}
match scope {
DependencyScope::Singleton => {
if let Some(cached) = self.get_singleton::<T>() {
return Ok(cached); }
}
DependencyScope::Request => {
if let Some(cached) = self.get_request::<T>() {
return Ok(cached); }
}
_ => {}
}
let _guard =
begin_scoped_resolution(type_id, type_name, scope).map_err(|e| match e {
crate::cycle_detection::CycleError::ScopeViolation { .. } => {
crate::DiError::ScopeError(e.to_string())
}
other => crate::DiError::CircularDependency(other.to_string()),
})?;
self.resolve_internal::<T>(scope, registry).await
})
.await
}
#[doc(hidden)]
pub async fn __resolve_from_registry<T: Any + Send + Sync + 'static>(
&self,
) -> crate::DiResult<Arc<T>> {
use crate::cycle_detection::{current_dependent_scope, current_dependent_type_name};
use crate::registry::DependencyScope;
let type_name = std::any::type_name::<T>();
let (scope, registry) = match self.find_type_registration::<T>() {
Some(entry) => entry,
None => {
if let Some(cached) = self.get_singleton::<T>() {
return Ok(cached);
}
if let Some(cached) = self.get_request::<T>() {
if !DependencyScope::Request.outlives(current_dependent_scope()) {
return Err(crate::DiError::ScopeError(format!(
"Scope violation: {:?}-scoped '{}' cannot resolve \
Request-scoped '{}' (pre-seeded); the dependency would \
be captured with a shorter lifetime",
current_dependent_scope(),
current_dependent_type_name(),
type_name,
)));
}
return Ok(cached);
}
return Err(crate::DiError::DependencyNotRegistered {
type_name: type_name.to_string(),
});
}
};
if !scope.outlives(current_dependent_scope()) {
return Err(crate::DiError::ScopeError(format!(
"Scope violation: {:?}-scoped '{}' cannot resolve \
{:?}-scoped '{}'; the dependency would be captured with \
a shorter lifetime",
current_dependent_scope(),
current_dependent_type_name(),
scope,
type_name,
)));
}
match scope {
DependencyScope::Singleton => {
if let Some(cached) = self.get_singleton::<T>() {
return Ok(cached);
}
}
DependencyScope::Request => {
if let Some(cached) = self.get_request::<T>() {
return Ok(cached);
}
}
_ => {}
}
self.resolve_internal::<T>(scope, registry).await
}
async fn resolve_internal<T: Any + Send + Sync + 'static>(
&self,
scope: crate::registry::DependencyScope,
registry: &Arc<crate::registry::DependencyRegistry>,
) -> crate::DiResult<Arc<T>> {
use crate::registry::DependencyScope;
match scope {
DependencyScope::Singleton => {
let instance = registry.create::<T>(self).await?;
self.set_singleton_arc(Arc::clone(&instance));
Ok(instance)
}
DependencyScope::Request => {
let instance = registry.create::<T>(self).await?;
self.set_request_arc(Arc::clone(&instance));
Ok(instance)
}
DependencyScope::Transient => {
registry.create::<T>(self).await
}
}
}
}
pub struct RequestContext {
injection_ctx: InjectionContext,
}
impl RequestContext {
pub fn new(singleton_scope: SingletonScope) -> Self {
Self {
injection_ctx: InjectionContext::builder(singleton_scope).build(),
}
}
pub fn injection_context(&self) -> &InjectionContext {
&self.injection_ctx
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
fn test_fork_for_request_shares_singleton_scope() {
let singleton_scope = Arc::new(SingletonScope::new());
singleton_scope.set(42u32);
let ctx = InjectionContext::builder(singleton_scope).build();
let request = HttpRequest::builder()
.method(hyper::Method::GET)
.uri("/test")
.build()
.unwrap();
let forked = ctx.fork_for_request(request);
let value = forked.get_singleton::<u32>();
assert_eq!(value.map(|v| *v), Some(42));
}
#[rstest]
fn test_fork_for_request_has_independent_request_scope() {
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
ctx.set_request("original".to_string());
let request = HttpRequest::builder()
.method(hyper::Method::GET)
.uri("/test")
.build()
.unwrap();
let forked = ctx.fork_for_request(request);
assert!(forked.get_request::<String>().is_none());
}
#[cfg(feature = "params")]
#[rstest]
fn test_fork_for_request_sets_http_request() {
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let request = HttpRequest::builder()
.method(hyper::Method::POST)
.uri("/api/users")
.build()
.unwrap();
let forked = ctx.fork_for_request(request);
let http_req = forked.get_http_request();
assert!(http_req.is_some());
assert_eq!(http_req.unwrap().method, hyper::Method::POST);
}
#[rstest]
fn test_fork_for_request_registers_http_request_in_request_scope() {
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let request = HttpRequest::builder()
.method(hyper::Method::POST)
.uri("/admin/api/server_fn/get_dashboard")
.build()
.unwrap();
let forked = ctx.fork_for_request(request);
let req_from_scope: Option<Arc<HttpRequest>> = forked.get_request();
assert!(req_from_scope.is_some());
let req = req_from_scope.unwrap();
assert_eq!(req.method, hyper::Method::POST);
assert_eq!(req.uri.path(), "/admin/api/server_fn/get_dashboard");
}
}