Skip to main content

reinhardt_di/
context.rs

1//! Injection context for dependency resolution
2
3use crate::function_handle::FunctionHandle;
4use crate::override_registry::OverrideRegistry;
5use crate::scope::{RequestScope, SingletonScope};
6use reinhardt_http::Request as HttpRequest;
7use std::any::Any;
8use std::sync::Arc;
9
10// Re-export ParamContext and Request types for convenience
11#[cfg(feature = "params")]
12pub use crate::params::{ParamContext, Request};
13
14/// The main injection context for dependency resolution.
15///
16/// `InjectionContext` manages both request-scoped and singleton-scoped dependencies,
17/// as well as dependency overrides for testing.
18///
19/// # Override Support
20///
21/// The context supports dependency overrides, which take precedence over normal
22/// dependency resolution. This is particularly useful for testing:
23///
24/// ```rust,no_run
25/// use reinhardt_di::{InjectionContext, SingletonScope};
26/// use std::sync::Arc;
27///
28/// # #[derive(Clone)]
29/// # struct Database;
30/// # impl Database {
31/// #     fn connect(_url: &str) -> Self { Database }
32/// #     fn mock() -> Self { Database }
33/// # }
34/// # fn create_database() -> Database { Database::connect("production://db") }
35///
36/// let singleton = Arc::new(SingletonScope::new());
37/// let ctx = InjectionContext::builder(singleton).build();
38///
39/// // Set override for testing
40/// ctx.dependency(create_database).override_with(Database::mock());
41/// ```
42pub struct InjectionContext {
43	request_scope: RequestScope,
44	singleton_scope: Arc<SingletonScope>,
45	/// Override registry for dependency substitution (e.g., for testing)
46	override_registry: Arc<OverrideRegistry>,
47	/// Per-context dependency registry that takes precedence over the global
48	/// registry during resolution. When `Some`, types registered here are
49	/// resolved first; types not found fall back to `global_registry()`.
50	registry: Option<Arc<crate::registry::DependencyRegistry>>,
51	/// HTTP request for parameter extraction
52	#[cfg(feature = "params")]
53	request: Option<Arc<Request>>,
54	/// Parameter context for path/header/cookie extraction
55	#[cfg(feature = "params")]
56	param_context: Option<Arc<ParamContext>>,
57}
58
59/// Builder for constructing `InjectionContext` instances.
60///
61/// Provides a fluent API for building injection contexts with optional HTTP request support.
62///
63/// # Examples
64///
65/// ```
66/// use reinhardt_di::{InjectionContext, SingletonScope};
67///
68/// let singleton_scope = SingletonScope::new();
69/// let ctx = InjectionContext::builder(singleton_scope).build();
70/// ```
71pub struct InjectionContextBuilder {
72	singleton_scope: Arc<SingletonScope>,
73	registry: Option<Arc<crate::registry::DependencyRegistry>>,
74	#[cfg(feature = "params")]
75	request: Option<Request>,
76	#[cfg(feature = "params")]
77	param_context: Option<ParamContext>,
78}
79
80impl InjectionContextBuilder {
81	/// Set the HTTP request for this context.
82	///
83	/// # Examples
84	///
85	/// ```no_run
86	/// use reinhardt_di::{InjectionContext, SingletonScope, Request};
87	///
88	/// let singleton_scope = SingletonScope::new();
89	/// let request = Request::builder()
90	///     .method(hyper::Method::GET)
91	///     .uri("/")
92	///     .build()
93	///     .unwrap();
94	///
95	/// let ctx = InjectionContext::builder(singleton_scope)
96	///     .with_request(request)
97	///     .build();
98	/// ```
99	#[cfg(feature = "params")]
100	pub fn with_request(mut self, request: Request) -> Self {
101		self.request = Some(request);
102		self
103	}
104
105	/// Set the parameter context for this context.
106	///
107	/// # Examples
108	///
109	/// ```no_run
110	/// use reinhardt_di::{InjectionContext, SingletonScope, ParamContext};
111	///
112	/// let singleton_scope = SingletonScope::new();
113	/// let param_context = ParamContext::new();
114	///
115	/// let ctx = InjectionContext::builder(singleton_scope)
116	///     .with_param_context(param_context)
117	///     .build();
118	/// ```
119	#[cfg(feature = "params")]
120	pub fn with_param_context(mut self, param_context: ParamContext) -> Self {
121		self.param_context = Some(param_context);
122		self
123	}
124
125	/// Attach a per-context dependency registry.
126	///
127	/// Types registered in this registry take precedence over the global
128	/// registry during [`InjectionContext::resolve`]. Types not found here
129	/// fall back to `global_registry()`.
130	pub fn with_registry(mut self, registry: Arc<crate::registry::DependencyRegistry>) -> Self {
131		self.registry = Some(registry);
132		self
133	}
134
135	/// Register a singleton instance in the context.
136	///
137	/// This allows explicit registration of pre-configured instances
138	/// that will be shared across all requests.
139	///
140	/// # Examples
141	///
142	/// ```
143	/// use reinhardt_di::{InjectionContext, SingletonScope};
144	/// use std::sync::Arc;
145	///
146	/// #[derive(Debug, Clone)]
147	/// struct DatabaseConfig {
148	///     url: String,
149	/// }
150	///
151	/// let singleton_scope = Arc::new(SingletonScope::new());
152	/// let config = DatabaseConfig { url: "postgres://localhost".to_string() };
153	///
154	/// let ctx = InjectionContext::builder(singleton_scope)
155	///     .singleton(config)
156	///     .build();
157	/// ```
158	pub fn singleton<T: std::any::Any + Send + Sync>(self, instance: T) -> Self {
159		self.singleton_scope.set(instance);
160		self
161	}
162
163	/// Build the final `InjectionContext` instance.
164	///
165	/// # Examples
166	///
167	/// ```
168	/// use reinhardt_di::{InjectionContext, SingletonScope};
169	///
170	/// let singleton_scope = SingletonScope::new();
171	/// let ctx = InjectionContext::builder(singleton_scope).build();
172	/// ```
173	pub fn build(self) -> InjectionContext {
174		InjectionContext {
175			request_scope: RequestScope::new(),
176			singleton_scope: self.singleton_scope,
177			override_registry: Arc::new(OverrideRegistry::new()),
178			registry: self.registry,
179			#[cfg(feature = "params")]
180			request: self.request.map(Arc::new),
181			#[cfg(feature = "params")]
182			param_context: self.param_context.map(Arc::new),
183		}
184	}
185}
186
187impl Clone for InjectionContext {
188	fn clone(&self) -> Self {
189		Self {
190			request_scope: self.request_scope.deep_clone(),
191			singleton_scope: Arc::clone(&self.singleton_scope),
192			override_registry: Arc::clone(&self.override_registry),
193			registry: self.registry.clone(),
194			#[cfg(feature = "params")]
195			request: self.request.clone(),
196			#[cfg(feature = "params")]
197			param_context: self.param_context.clone(),
198		}
199	}
200}
201
202impl InjectionContext {
203	/// Create a new `InjectionContextBuilder`.
204	///
205	/// This is the recommended way to construct an `InjectionContext`.
206	///
207	/// # Examples
208	///
209	/// ```
210	/// use reinhardt_di::{InjectionContext, SingletonScope};
211	///
212	/// let singleton_scope = SingletonScope::new();
213	/// let ctx = InjectionContext::builder(singleton_scope).build();
214	/// ```
215	pub fn builder(singleton_scope: impl Into<Arc<SingletonScope>>) -> InjectionContextBuilder {
216		InjectionContextBuilder {
217			singleton_scope: singleton_scope.into(),
218			registry: None,
219			#[cfg(feature = "params")]
220			request: None,
221			#[cfg(feature = "params")]
222			param_context: None,
223		}
224	}
225
226	/// Gets the HTTP request from the context.
227	///
228	/// Returns `None` if no request was set (e.g., when testing without HTTP context).
229	/// Returns a reference to the request.
230	///
231	/// # Examples
232	///
233	/// ```no_run
234	/// use reinhardt_di::{InjectionContext, SingletonScope, Request, ParamContext};
235	///
236	/// let singleton_scope = SingletonScope::new();
237	/// let request = Request::builder()
238	///     .method(hyper::Method::GET)
239	///     .uri("/")
240	///     .build()
241	///     .unwrap();
242	/// let param_context = ParamContext::new();
243	///
244	/// let ctx = InjectionContext::builder(singleton_scope)
245	///     .with_request(request)
246	///     .with_param_context(param_context)
247	///     .build();
248	///
249	/// assert!(ctx.get_http_request().is_some());
250	/// ```
251	#[cfg(feature = "params")]
252	pub fn get_http_request(&self) -> Option<&Request> {
253		self.request.as_ref().map(|arc| arc.as_ref())
254	}
255
256	/// Gets the parameter context from the context.
257	///
258	/// Returns `None` if no parameter context was set.
259	/// Returns a reference to the parameter context.
260	///
261	/// # Examples
262	///
263	/// ```no_run
264	/// use reinhardt_di::{InjectionContext, SingletonScope, Request, ParamContext};
265	///
266	/// let singleton_scope = SingletonScope::new();
267	/// let request = Request::builder()
268	///     .method(hyper::Method::GET)
269	///     .uri("/")
270	///     .build()
271	///     .unwrap();
272	/// let param_context = ParamContext::new();
273	///
274	/// let ctx = InjectionContext::builder(singleton_scope)
275	///     .with_request(request)
276	///     .with_param_context(param_context)
277	///     .build();
278	///
279	/// assert!(ctx.get_param_context().is_some());
280	/// ```
281	#[cfg(feature = "params")]
282	pub fn get_param_context(&self) -> Option<&ParamContext> {
283		self.param_context.as_ref().map(|arc| arc.as_ref())
284	}
285
286	/// Sets the HTTP request and parameter context.
287	///
288	/// This can be used to add HTTP context to an existing InjectionContext.
289	/// The request and parameter context will be wrapped in Arc internally.
290	///
291	/// # Examples
292	///
293	/// ```no_run
294	/// use reinhardt_di::{InjectionContext, SingletonScope, Request, ParamContext};
295	///
296	/// let singleton_scope = SingletonScope::new();
297	/// let mut ctx = InjectionContext::builder(singleton_scope).build();
298	///
299	/// let request = Request::builder()
300	///     .method(hyper::Method::GET)
301	///     .uri("/")
302	///     .build()
303	///     .unwrap();
304	/// let param_context = ParamContext::new();
305	///
306	/// ctx.set_http_request(request, param_context);
307	/// ```
308	#[cfg(feature = "params")]
309	pub fn set_http_request(&mut self, request: Request, param_context: ParamContext) {
310		self.request = Some(Arc::new(request));
311		self.param_context = Some(Arc::new(param_context));
312	}
313	/// Retrieves a request-scoped value from the context.
314	///
315	/// Request-scoped values are cached only for the duration of a single request.
316	///
317	/// # Examples
318	///
319	/// ```
320	/// use reinhardt_di::{InjectionContext, SingletonScope};
321	/// use std::sync::Arc;
322	///
323	/// let singleton_scope = Arc::new(SingletonScope::new());
324	/// let ctx = InjectionContext::builder(singleton_scope).build();
325	///
326	/// ctx.set_request(42i32);
327	/// let value = ctx.get_request::<i32>().unwrap();
328	/// assert_eq!(*value, 42);
329	/// ```
330	pub fn get_request<T: Any + Send + Sync>(&self) -> Option<Arc<T>> {
331		self.request_scope.get::<T>()
332	}
333	/// Stores a value in the request scope.
334	///
335	/// The value is cached for the duration of the current request only.
336	///
337	/// # Examples
338	///
339	/// ```
340	/// use reinhardt_di::{InjectionContext, SingletonScope};
341	/// use std::sync::Arc;
342	///
343	/// let singleton_scope = Arc::new(SingletonScope::new());
344	/// let ctx = InjectionContext::builder(singleton_scope).build();
345	///
346	/// ctx.set_request("request-data".to_string());
347	/// assert!(ctx.get_request::<String>().is_some());
348	/// ```
349	pub fn set_request<T: Any + Send + Sync>(&self, value: T) {
350		self.request_scope.set(value);
351	}
352
353	/// Stores a pre-wrapped `Arc<T>` in the request scope.
354	///
355	/// This avoids the need to unwrap and re-wrap Arc values that are
356	/// already in Arc form, such as those returned by factory functions.
357	pub(crate) fn set_request_arc<T: Any + Send + Sync>(&self, value: Arc<T>) {
358		self.request_scope.set_arc(value);
359	}
360	/// Retrieves a singleton value from the context.
361	///
362	/// Singleton values persist across all requests and are shared application-wide.
363	///
364	/// # Examples
365	///
366	/// ```
367	/// use reinhardt_di::{InjectionContext, SingletonScope};
368	/// use std::sync::Arc;
369	///
370	/// let singleton_scope = Arc::new(SingletonScope::new());
371	/// singleton_scope.set(100u64);
372	///
373	/// let ctx = InjectionContext::builder(singleton_scope).build();
374	/// let value = ctx.get_singleton::<u64>().unwrap();
375	/// assert_eq!(*value, 100);
376	/// ```
377	pub fn get_singleton<T: Any + Send + Sync>(&self) -> Option<Arc<T>> {
378		self.singleton_scope.get::<T>()
379	}
380	/// Stores a value in the singleton scope.
381	///
382	/// The value persists across all requests and is shared application-wide.
383	///
384	/// # Examples
385	///
386	/// ```
387	/// use reinhardt_di::{InjectionContext, SingletonScope};
388	/// use std::sync::Arc;
389	///
390	/// let singleton_scope = Arc::new(SingletonScope::new());
391	/// let ctx = InjectionContext::builder(singleton_scope).build();
392	///
393	/// ctx.set_singleton("global-config".to_string());
394	/// assert!(ctx.get_singleton::<String>().is_some());
395	/// ```
396	pub fn set_singleton<T: Any + Send + Sync>(&self, value: T) {
397		self.singleton_scope.set(value);
398	}
399
400	/// Stores a pre-wrapped `Arc<T>` in the singleton scope.
401	///
402	/// This avoids the need to unwrap and re-wrap Arc values that are
403	/// already in Arc form, such as those returned by factory functions.
404	fn set_singleton_arc<T: Any + Send + Sync>(&self, value: Arc<T>) {
405		self.singleton_scope.set_arc(value);
406	}
407
408	/// Returns a reference to the singleton scope.
409	///
410	/// This is useful for advanced scenarios where direct access to the
411	/// singleton scope is needed.
412	pub fn singleton_scope(&self) -> &Arc<SingletonScope> {
413		&self.singleton_scope
414	}
415
416	/// Internal helper for creating forked contexts.
417	///
418	/// Centralizes the construction logic shared between `fork()` and
419	/// `fork_for_request()` to prevent field-drift when new fields are added.
420	fn fork_inner(
421		&self,
422		#[cfg(feature = "params")] request: Option<Arc<HttpRequest>>,
423		#[cfg(feature = "params")] param_context: Option<Arc<ParamContext>>,
424	) -> InjectionContext {
425		InjectionContext {
426			request_scope: RequestScope::new(),
427			singleton_scope: self.singleton_scope.clone(),
428			override_registry: self.override_registry.clone(),
429			registry: self.registry.clone(),
430			#[cfg(feature = "params")]
431			request,
432			#[cfg(feature = "params")]
433			param_context,
434		}
435	}
436
437	/// Creates a per-request fork of this context with an HTTP request.
438	///
439	/// The forked context shares the same singleton scope but has a fresh
440	/// request scope. When the `params` feature is enabled, the HTTP request
441	/// and its path parameters are made available for parameter extraction.
442	///
443	/// The HTTP request is stored in both the dedicated `request` field
444	/// (for [`get_http_request()`](Self::get_http_request)) and the
445	/// `request_scope` (for [`get_request::<HttpRequest>()`](Self::get_request)),
446	/// ensuring that `Injectable` types such as `ServerFnRequest` can
447	/// retrieve it via either accessor.
448	pub fn fork_for_request(&self, request: HttpRequest) -> InjectionContext {
449		let request_arc = Arc::new(request);
450
451		#[cfg(feature = "params")]
452		let param_context = Some(Arc::new(ParamContext::with_path_params(
453			request_arc.path_params.clone(),
454		)));
455
456		let ctx = self.fork_inner(
457			#[cfg(feature = "params")]
458			Some(Arc::clone(&request_arc)),
459			#[cfg(feature = "params")]
460			param_context,
461		);
462
463		// Also register in request_scope so that Injectable types
464		// (e.g. ServerFnRequest, ServerFnBody) can retrieve it via
465		// get_request::<HttpRequest>()
466		ctx.set_request_arc(request_arc);
467
468		ctx
469	}
470
471	/// Creates a per-request fork of this context without an HTTP request.
472	///
473	/// The forked context shares the same singleton scope but has a fresh
474	/// request scope. Unlike `fork_for_request`, this method does not store
475	/// an HTTP request, so path parameter extraction is not available.
476	///
477	/// This is intended for non-HTTP protocols (gRPC, GraphQL) where
478	/// request-scoped isolation is needed but HTTP request data is not available.
479	pub fn fork(&self) -> InjectionContext {
480		self.fork_inner(
481			#[cfg(feature = "params")]
482			None,
483			#[cfg(feature = "params")]
484			None,
485		)
486	}
487
488	/// Returns a reference to the override registry.
489	///
490	/// The override registry stores function-level overrides that take
491	/// precedence over normal dependency resolution.
492	pub fn overrides(&self) -> &OverrideRegistry {
493		&self.override_registry
494	}
495
496	/// Creates a handle for the given injectable function.
497	///
498	/// This method provides a fluent API for setting and managing dependency
499	/// overrides. The function pointer is used as a unique key to identify
500	/// which injectable function should be overridden.
501	///
502	/// # Note
503	///
504	/// This method is designed to work with functions annotated with `#[injectable]`.
505	/// The `#[injectable]` macro generates a 0-argument function regardless of
506	/// the original function's parameter count, as all `#[inject]` parameters
507	/// are resolved internally by the DI system.
508	///
509	/// # Type Parameters
510	///
511	/// * `O` - The output type of the function (the dependency type)
512	///
513	/// # Arguments
514	///
515	/// * `func` - A function pointer to the injectable function
516	///
517	/// # Examples
518	///
519	/// ```rust,no_run
520	/// use reinhardt_di::{InjectionContext, SingletonScope};
521	/// use std::sync::Arc;
522	///
523	/// # #[derive(Clone)]
524	/// # struct Database;
525	/// # impl Database {
526	/// #     fn connect(_url: &str) -> Self { Database }
527	/// #     fn mock() -> Self { Database }
528	/// # }
529	/// # struct Config { url: String }
530	/// # fn create_database() -> Database {
531	/// #     Database::connect("production://db")
532	/// # }
533	///
534	/// let singleton = Arc::new(SingletonScope::new());
535	/// let ctx = InjectionContext::builder(singleton).build();
536	///
537	/// // Set override - create_database is 0-argument after macro expansion
538	/// ctx.dependency(create_database).override_with(Database::mock());
539	///
540	/// // Check if override exists
541	/// assert!(ctx.dependency(create_database).has_override());
542	///
543	/// // Clear override
544	/// ctx.dependency(create_database).clear_override();
545	/// ```
546	pub fn dependency<O>(&self, func: fn() -> O) -> FunctionHandle<'_, O>
547	where
548		O: Clone + Send + Sync + 'static,
549	{
550		let func_ptr = func as usize;
551		FunctionHandle::new(self, func_ptr)
552	}
553
554	/// Gets an override value for a function pointer.
555	///
556	/// This is primarily used internally by the `#[injectable]` macro to check
557	/// for overrides before executing the actual function.
558	///
559	/// # Arguments
560	///
561	/// * `func_ptr` - The function pointer address as usize
562	///
563	/// # Returns
564	///
565	/// `Some(value)` if an override is set, `None` otherwise.
566	pub fn get_override<O: Clone + 'static>(&self, func_ptr: usize) -> Option<O> {
567		self.override_registry.get(func_ptr)
568	}
569
570	/// Clears all overrides from the context.
571	///
572	/// This is useful for cleanup in tests to ensure a clean state.
573	///
574	/// # Examples
575	///
576	/// ```rust
577	/// use reinhardt_di::{InjectionContext, SingletonScope};
578	/// use std::sync::Arc;
579	///
580	/// fn my_factory() -> i32 { 42 }
581	///
582	/// let singleton = Arc::new(SingletonScope::new());
583	/// let ctx = InjectionContext::builder(singleton).build();
584	///
585	/// ctx.dependency(my_factory).override_with(100);
586	/// assert!(ctx.dependency(my_factory).has_override());
587	///
588	/// ctx.clear_overrides();
589	/// assert!(!ctx.dependency(my_factory).has_override());
590	/// ```
591	pub fn clear_overrides(&self) {
592		self.override_registry.clear();
593	}
594
595	/// Layered registry lookup: per-context registry takes precedence, then
596	/// global. Returns the scope and which registry owns the type.
597	fn find_type_registration<T: Any + Send + Sync + 'static>(
598		&self,
599	) -> Option<(
600		crate::registry::DependencyScope,
601		&Arc<crate::registry::DependencyRegistry>,
602	)> {
603		use crate::registry::global_registry;
604
605		if let Some(ref reg) = self.registry
606			&& let Some(scope) = reg.get_scope::<T>()
607		{
608			return Some((scope, reg));
609		}
610		let global = global_registry();
611		global.get_scope::<T>().map(|scope| (scope, global))
612	}
613
614	/// Resolve a dependency by type.
615	///
616	/// Resolution checks the per-context registry first (if set via
617	/// [`InjectionContextBuilder::with_registry`]), then falls back to the
618	/// global registry. Scope caches (singleton / request) are consulted
619	/// before invoking the factory.
620	///
621	/// # Examples
622	///
623	/// ```no_run
624	/// use reinhardt_di::{InjectionContext, SingletonScope};
625	/// use std::sync::Arc;
626	/// # use async_trait::async_trait;
627	///
628	/// # #[derive(Clone)]
629	/// # struct Config;
630	/// # #[async_trait]
631	/// # impl reinhardt_di::Injectable for Config {
632	/// #     async fn inject(_ctx: &InjectionContext) -> reinhardt_di::DiResult<Self> {
633	/// #         Ok(Config)
634	/// #     }
635	/// # }
636	/// # async fn example() -> reinhardt_di::DiResult<()> {
637	/// let singleton_scope = Arc::new(SingletonScope::new());
638	/// let ctx = InjectionContext::builder(singleton_scope).build();
639	///
640	/// let config = ctx.resolve::<Config>().await?;
641	/// # Ok(())
642	/// # }
643	/// ```
644	pub async fn resolve<T: Any + Send + Sync + 'static>(&self) -> crate::DiResult<Arc<T>> {
645		use crate::cycle_detection::{
646			begin_scoped_resolution, current_dependent_scope, current_dependent_type_name,
647			register_type_name, with_cycle_detection_scope,
648		};
649		use crate::registry::DependencyScope;
650
651		with_cycle_detection_scope(async {
652			let type_id = std::any::TypeId::of::<T>();
653			let type_name = std::any::type_name::<T>();
654
655			// Register type name (for error messages)
656			register_type_name::<T>(type_name);
657
658			// [Fast path] Skip circular detection on cache hit
659			let (scope, registry) = match self.find_type_registration::<T>() {
660				Some(entry) => entry,
661				None => {
662					// Fallback: check scope caches for types pre-seeded via
663					// SingletonScope::set() / InjectionContext::set_request()
664					// (e.g., types registered through DiRegistrationList).
665					// Singleton pre-seeds are safe for any dependent scope.
666					if let Some(cached) = self.get_singleton::<T>() {
667						return Ok(cached);
668					}
669					// Request pre-seeds are request-scoped: apply the same
670					// hierarchy check as registry-registered types.
671					if let Some(cached) = self.get_request::<T>() {
672						if !DependencyScope::Request.outlives(current_dependent_scope()) {
673							return Err(crate::DiError::ScopeError(format!(
674								"Scope violation: {:?}-scoped '{}' cannot resolve \
675								 Request-scoped '{}' (pre-seeded); the dependency would \
676								 be captured with a shorter lifetime",
677								current_dependent_scope(),
678								current_dependent_type_name(),
679								type_name,
680							)));
681						}
682						return Ok(cached);
683					}
684					return Err(crate::DiError::DependencyNotRegistered {
685						type_name: type_name.to_string(),
686					});
687				}
688			};
689
690			// Scope hierarchy check — runs on EVERY resolution (cache hit or miss).
691			// A singleton dependent must not resolve a request-scoped dependency,
692			// even if that dependency is already cached from a prior request.
693			if !scope.outlives(current_dependent_scope()) {
694				return Err(crate::DiError::ScopeError(format!(
695					"Scope violation: {:?}-scoped '{}' cannot resolve \
696					 {:?}-scoped '{}'; the dependency would be captured with \
697					 a shorter lifetime",
698					current_dependent_scope(),
699					current_dependent_type_name(),
700					scope,
701					type_name,
702				)));
703			}
704
705			match scope {
706				DependencyScope::Singleton => {
707					if let Some(cached) = self.get_singleton::<T>() {
708						return Ok(cached); // < 5% overhead
709					}
710				}
711				DependencyScope::Request => {
712					if let Some(cached) = self.get_request::<T>() {
713						return Ok(cached); // < 5% overhead
714					}
715				}
716				_ => {}
717			}
718
719			// [Slow path] Scope-aware resolution with cycle detection
720			let _guard =
721				begin_scoped_resolution(type_id, type_name, scope).map_err(|e| match e {
722					crate::cycle_detection::CycleError::ScopeViolation { .. } => {
723						crate::DiError::ScopeError(e.to_string())
724					}
725					other => crate::DiError::CircularDependency(other.to_string()),
726				})?;
727
728			// Actual resolution processing (existing logic)
729			self.resolve_internal::<T>(scope, registry).await
730			// Guard is automatically cleaned up when dropped
731		})
732		.await
733	}
734
735	/// Resolve a type from the registry, bypassing cycle detection.
736	///
737	/// Used by the `Injectable` impl generated by `#[injectable_factory]`.
738	/// The caller (`Depends::resolve`) has already registered the type in the
739	/// cycle detection stack, so a second `begin_resolution` for the same type
740	/// would trigger a false circular dependency error.
741	///
742	/// This method performs cache lookup and delegates to `resolve_internal`
743	/// without adding a cycle detection guard.
744	#[doc(hidden)]
745	pub async fn __resolve_from_registry<T: Any + Send + Sync + 'static>(
746		&self,
747	) -> crate::DiResult<Arc<T>> {
748		use crate::cycle_detection::{current_dependent_scope, current_dependent_type_name};
749		use crate::registry::DependencyScope;
750
751		let type_name = std::any::type_name::<T>();
752		let (scope, registry) = match self.find_type_registration::<T>() {
753			Some(entry) => entry,
754			None => {
755				// Fallback: check scope caches for pre-seeded types
756				if let Some(cached) = self.get_singleton::<T>() {
757					return Ok(cached);
758				}
759				if let Some(cached) = self.get_request::<T>() {
760					if !DependencyScope::Request.outlives(current_dependent_scope()) {
761						return Err(crate::DiError::ScopeError(format!(
762							"Scope violation: {:?}-scoped '{}' cannot resolve \
763							 Request-scoped '{}' (pre-seeded); the dependency would \
764							 be captured with a shorter lifetime",
765							current_dependent_scope(),
766							current_dependent_type_name(),
767							type_name,
768						)));
769					}
770					return Ok(cached);
771				}
772				return Err(crate::DiError::DependencyNotRegistered {
773					type_name: type_name.to_string(),
774				});
775			}
776		};
777
778		// Scope hierarchy check — same as resolve()
779		if !scope.outlives(current_dependent_scope()) {
780			return Err(crate::DiError::ScopeError(format!(
781				"Scope violation: {:?}-scoped '{}' cannot resolve \
782				 {:?}-scoped '{}'; the dependency would be captured with \
783				 a shorter lifetime",
784				current_dependent_scope(),
785				current_dependent_type_name(),
786				scope,
787				type_name,
788			)));
789		}
790
791		// Fast cache check (same as resolve)
792		match scope {
793			DependencyScope::Singleton => {
794				if let Some(cached) = self.get_singleton::<T>() {
795					return Ok(cached);
796				}
797			}
798			DependencyScope::Request => {
799				if let Some(cached) = self.get_request::<T>() {
800					return Ok(cached);
801				}
802			}
803			_ => {}
804		}
805
806		self.resolve_internal::<T>(scope, registry).await
807	}
808
809	async fn resolve_internal<T: Any + Send + Sync + 'static>(
810		&self,
811		scope: crate::registry::DependencyScope,
812		registry: &Arc<crate::registry::DependencyRegistry>,
813	) -> crate::DiResult<Arc<T>> {
814		use crate::registry::DependencyScope;
815
816		match scope {
817			DependencyScope::Singleton => {
818				// Create new instance
819				let instance = registry.create::<T>(self).await?;
820
821				// Cache the Arc directly in singleton scope without unwrapping.
822				// This avoids panics when the factory retains an Arc clone,
823				// which causes Arc::try_unwrap to fail.
824				self.set_singleton_arc(Arc::clone(&instance));
825				Ok(instance)
826			}
827			DependencyScope::Request => {
828				// Create new instance
829				let instance = registry.create::<T>(self).await?;
830
831				// Cache the Arc directly in request scope without unwrapping.
832				// This avoids panics when the factory retains an Arc clone.
833				self.set_request_arc(Arc::clone(&instance));
834				Ok(instance)
835			}
836			DependencyScope::Transient => {
837				// Never cache, always create new
838				registry.create::<T>(self).await
839			}
840		}
841	}
842}
843
844/// Context for per-request dependency injection resolution.
845///
846/// Wraps an `InjectionContext` with request-scoped lifetime management.
847pub struct RequestContext {
848	injection_ctx: InjectionContext,
849}
850
851impl RequestContext {
852	/// Creates a new RequestContext with a shared singleton scope.
853	///
854	/// This is typically used to create a context for each incoming request.
855	///
856	/// # Examples
857	///
858	/// ```
859	/// use reinhardt_di::{RequestContext, SingletonScope};
860	///
861	/// let singleton_scope = SingletonScope::new();
862	/// let request_ctx = RequestContext::new(singleton_scope);
863	/// ```
864	pub fn new(singleton_scope: SingletonScope) -> Self {
865		Self {
866			injection_ctx: InjectionContext::builder(singleton_scope).build(),
867		}
868	}
869	/// Returns a reference to the underlying injection context.
870	///
871	/// This allows access to the dependency injection context for resolving dependencies.
872	///
873	/// # Examples
874	///
875	/// ```
876	/// use reinhardt_di::{RequestContext, SingletonScope};
877	///
878	/// let singleton_scope = SingletonScope::new();
879	/// let request_ctx = RequestContext::new(singleton_scope);
880	///
881	/// let ctx = request_ctx.injection_context();
882	/// ctx.set_request(42i32);
883	/// ```
884	pub fn injection_context(&self) -> &InjectionContext {
885		&self.injection_ctx
886	}
887}
888
889#[cfg(test)]
890mod tests {
891	use super::*;
892	use rstest::rstest;
893
894	#[rstest]
895	fn test_fork_for_request_shares_singleton_scope() {
896		// Arrange
897		let singleton_scope = Arc::new(SingletonScope::new());
898		singleton_scope.set(42u32);
899		let ctx = InjectionContext::builder(singleton_scope).build();
900
901		let request = HttpRequest::builder()
902			.method(hyper::Method::GET)
903			.uri("/test")
904			.build()
905			.unwrap();
906
907		// Act
908		let forked = ctx.fork_for_request(request);
909
910		// Assert - singleton scope is shared
911		let value = forked.get_singleton::<u32>();
912		assert_eq!(value.map(|v| *v), Some(42));
913	}
914
915	#[rstest]
916	fn test_fork_for_request_has_independent_request_scope() {
917		// Arrange
918		let singleton_scope = Arc::new(SingletonScope::new());
919		let ctx = InjectionContext::builder(singleton_scope).build();
920		ctx.set_request("original".to_string());
921
922		let request = HttpRequest::builder()
923			.method(hyper::Method::GET)
924			.uri("/test")
925			.build()
926			.unwrap();
927
928		// Act
929		let forked = ctx.fork_for_request(request);
930
931		// Assert - request scope is independent (not inherited)
932		assert!(forked.get_request::<String>().is_none());
933	}
934
935	#[cfg(feature = "params")]
936	#[rstest]
937	fn test_fork_for_request_sets_http_request() {
938		// Arrange
939		let singleton_scope = Arc::new(SingletonScope::new());
940		let ctx = InjectionContext::builder(singleton_scope).build();
941
942		let request = HttpRequest::builder()
943			.method(hyper::Method::POST)
944			.uri("/api/users")
945			.build()
946			.unwrap();
947
948		// Act
949		let forked = ctx.fork_for_request(request);
950
951		// Assert - HTTP request is available
952		let http_req = forked.get_http_request();
953		assert!(http_req.is_some());
954		assert_eq!(http_req.unwrap().method, hyper::Method::POST);
955	}
956
957	#[rstest]
958	fn test_fork_for_request_registers_http_request_in_request_scope() {
959		// Arrange
960		let singleton_scope = Arc::new(SingletonScope::new());
961		let ctx = InjectionContext::builder(singleton_scope).build();
962
963		let request = HttpRequest::builder()
964			.method(hyper::Method::POST)
965			.uri("/admin/api/server_fn/get_dashboard")
966			.build()
967			.unwrap();
968
969		// Act
970		let forked = ctx.fork_for_request(request);
971
972		// Assert - HTTP request is retrievable via get_request::<HttpRequest>()
973		// This is the accessor used by Injectable types like ServerFnRequest
974		let req_from_scope: Option<Arc<HttpRequest>> = forked.get_request();
975		assert!(req_from_scope.is_some());
976		let req = req_from_scope.unwrap();
977		assert_eq!(req.method, hyper::Method::POST);
978		assert_eq!(req.uri.path(), "/admin/api/server_fn/get_dashboard");
979	}
980}