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