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