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}