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