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}