reinhardt_di/injected.rs
1//! Injected wrapper for dependency injection
2//!
3//! FastAPI-inspired dependency injection wrapper that provides:
4//! - Automatic dependency resolution
5//! - Caching control via scope and cache flag
6//! - Type-safe dependency injection with metadata
7//!
8//! # Examples
9//!
10//! ```
11//! use reinhardt_di::{Injected, OptionalInjected, Injectable, InjectionContext};
12//!
13//! # #[derive(Clone, Default)]
14//! # struct Database;
15//! # #[derive(Clone, Default)]
16//! # struct Cache;
17//! #
18//! # #[async_trait::async_trait]
19//! # impl Injectable for Database {
20//! # async fn inject(ctx: &InjectionContext) -> reinhardt_di::DiResult<Self> {
21//! # Ok(Database::default())
22//! # }
23//! # }
24//! #
25//! # #[async_trait::async_trait]
26//! # impl Injectable for Cache {
27//! # async fn inject(ctx: &InjectionContext) -> reinhardt_di::DiResult<Self> {
28//! # Ok(Cache::default())
29//! # }
30//! # }
31//! #
32//! async fn handler(
33//! db: Injected<Database>,
34//! optional_cache: OptionalInjected<Cache>,
35//! ) -> String {
36//! // db is always available
37//! // optional_cache can be treated as Option<Injected<Cache>>
38//! "OK".to_string()
39//! }
40//! ```
41
42use crate::{
43 DiError, DiResult, Injectable, InjectionContext, begin_resolution, with_cycle_detection_scope,
44};
45use std::any::TypeId;
46use std::ops::Deref;
47use std::sync::Arc;
48
49/// Injection metadata
50///
51/// Tracks the scope and caching status of an injected dependency.
52#[derive(Debug, Clone, Copy)]
53pub struct InjectionMetadata {
54 /// Dependency scope (Request or Singleton)
55 pub scope: DependencyScope,
56 /// Whether caching was enabled during resolution
57 pub cached: bool,
58}
59
60/// Dependency scope
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum DependencyScope {
63 /// Request-scoped dependency (lifetime tied to request)
64 Request,
65 /// Singleton-scoped dependency (shared across requests)
66 Singleton,
67}
68
69/// Injected dependency wrapper
70///
71/// Wraps an `Arc<T>` with injection metadata, providing:
72/// - Shared ownership via `Arc`
73/// - Metadata tracking (scope, cache status)
74/// - Transparent access via `Deref`
75///
76/// # Examples
77///
78/// ```
79/// use reinhardt_di::{Injected, InjectionContext, Injectable, SingletonScope};
80/// use std::sync::Arc;
81///
82/// # #[derive(Clone, Default)]
83/// # struct Config;
84/// #
85/// # #[async_trait::async_trait]
86/// # impl Injectable for Config {
87/// # async fn inject(ctx: &InjectionContext) -> reinhardt_di::DiResult<Self> {
88/// # Ok(Config::default())
89/// # }
90/// # }
91/// #
92/// # async fn example() -> reinhardt_di::DiResult<()> {
93/// let singleton_scope = Arc::new(SingletonScope::new());
94/// let ctx = InjectionContext::builder(singleton_scope).build();
95///
96/// // Resolve with cache enabled (default)
97/// let config1 = Injected::<Config>::resolve(&ctx).await?;
98/// let config2 = Injected::<Config>::resolve(&ctx).await?;
99///
100/// // Resolve without cache
101/// let config3 = Injected::<Config>::resolve_uncached(&ctx).await?;
102/// # Ok(())
103/// # }
104/// ```
105#[deprecated(
106 since = "0.1.0-rc.16",
107 note = "use `Depends<T>` instead. `Injected<T>` will be removed in a future version."
108)]
109#[derive(Debug)]
110pub struct Injected<T: Injectable> {
111 inner: Arc<T>,
112 metadata: InjectionMetadata,
113}
114
115#[allow(deprecated)]
116impl<T: Injectable> Injected<T> {
117 /// Resolve dependency with cache enabled (default)
118 ///
119 /// # Examples
120 ///
121 /// ```
122 /// use reinhardt_di::{Injected, InjectionContext, Injectable, SingletonScope};
123 /// use std::sync::Arc;
124 ///
125 /// # #[derive(Clone, Default)]
126 /// # struct Config;
127 /// #
128 /// # #[async_trait::async_trait]
129 /// # impl Injectable for Config {
130 /// # async fn inject(ctx: &InjectionContext) -> reinhardt_di::DiResult<Self> {
131 /// # Ok(Config::default())
132 /// # }
133 /// # }
134 /// #
135 /// # async fn example() -> reinhardt_di::DiResult<()> {
136 /// let singleton_scope = Arc::new(SingletonScope::new());
137 /// let ctx = InjectionContext::builder(singleton_scope).build();
138 /// let config = Injected::<Config>::resolve(&ctx).await?;
139 /// # Ok(())
140 /// # }
141 /// ```
142 pub async fn resolve(ctx: &InjectionContext) -> DiResult<Self> {
143 Self::resolve_with_cache(ctx, true).await
144 }
145
146 /// Resolve dependency without cache
147 ///
148 /// # Examples
149 ///
150 /// ```
151 /// use reinhardt_di::{Injected, InjectionContext, Injectable, SingletonScope};
152 /// use std::sync::Arc;
153 ///
154 /// # #[derive(Clone, Default)]
155 /// # struct Config;
156 /// #
157 /// # #[async_trait::async_trait]
158 /// # impl Injectable for Config {
159 /// # async fn inject(ctx: &InjectionContext) -> reinhardt_di::DiResult<Self> {
160 /// # Ok(Config::default())
161 /// # }
162 /// # }
163 /// #
164 /// # async fn example() -> reinhardt_di::DiResult<()> {
165 /// let singleton_scope = Arc::new(SingletonScope::new());
166 /// let ctx = InjectionContext::builder(singleton_scope).build();
167 /// let config = Injected::<Config>::resolve_uncached(&ctx).await?;
168 /// # Ok(())
169 /// # }
170 /// ```
171 pub async fn resolve_uncached(ctx: &InjectionContext) -> DiResult<Self> {
172 Self::resolve_with_cache(ctx, false).await
173 }
174
175 /// Resolve dependency with cache control (internal use)
176 ///
177 /// # Arguments
178 ///
179 /// * `ctx` - Injection context
180 /// * `use_cache` - Whether to use request-scoped cache
181 async fn resolve_with_cache(ctx: &InjectionContext, use_cache: bool) -> DiResult<Self> {
182 with_cycle_detection_scope(async {
183 let inner = if use_cache {
184 // Check request cache first
185 if let Some(cached) = ctx.get_request::<T>() {
186 cached
187 } else {
188 // Begin circular dependency detection
189 let type_id = TypeId::of::<T>();
190 let type_name = std::any::type_name::<T>();
191 let _guard = begin_resolution(type_id, type_name)
192 .map_err(|e| DiError::CircularDependency(e.to_string()))?;
193
194 let v = T::inject(ctx).await?;
195 let arc = Arc::new(v);
196 ctx.set_request_arc(Arc::clone(&arc));
197 arc
198 }
199 } else {
200 // Begin circular dependency detection (even for uncached)
201 let type_id = TypeId::of::<T>();
202 let type_name = std::any::type_name::<T>();
203 let _guard = begin_resolution(type_id, type_name)
204 .map_err(|e| DiError::CircularDependency(e.to_string()))?;
205
206 // Skip cache
207 Arc::new(T::inject_uncached(ctx).await?)
208 };
209
210 Ok(Self {
211 inner,
212 metadata: InjectionMetadata {
213 scope: DependencyScope::Request,
214 cached: use_cache,
215 },
216 })
217 })
218 .await
219 }
220
221 /// Create from value for testing
222 ///
223 /// # Examples
224 ///
225 /// ```
226 /// use reinhardt_di::{Injected, Injectable};
227 ///
228 /// # #[derive(Clone, Default)]
229 /// # struct Database {
230 /// # connection_count: usize,
231 /// # }
232 /// #
233 /// # #[async_trait::async_trait]
234 /// # impl Injectable for Database {
235 /// # async fn inject(ctx: &reinhardt_di::InjectionContext) -> reinhardt_di::DiResult<Self> {
236 /// # Ok(Database::default())
237 /// # }
238 /// # }
239 /// #
240 /// let db = Database { connection_count: 10 };
241 /// let injected = Injected::from_value(db);
242 /// assert_eq!(injected.connection_count, 10);
243 /// ```
244 pub fn from_value(value: T) -> Self {
245 Self {
246 inner: Arc::new(value),
247 metadata: InjectionMetadata {
248 scope: DependencyScope::Request,
249 cached: false,
250 },
251 }
252 }
253
254 /// Get Arc reference
255 ///
256 /// # Examples
257 ///
258 /// ```
259 /// use reinhardt_di::{Injected, Injectable};
260 /// use std::sync::Arc;
261 ///
262 /// # #[derive(Clone, Default)]
263 /// # struct Config;
264 /// #
265 /// # #[async_trait::async_trait]
266 /// # impl Injectable for Config {
267 /// # async fn inject(ctx: &reinhardt_di::InjectionContext) -> reinhardt_di::DiResult<Self> {
268 /// # Ok(Config::default())
269 /// # }
270 /// # }
271 /// #
272 /// let injected = Injected::from_value(Config::default());
273 /// let arc: &Arc<Config> = injected.as_arc();
274 /// ```
275 pub fn as_arc(&self) -> &Arc<T> {
276 &self.inner
277 }
278
279 /// Get injection metadata
280 ///
281 /// # Examples
282 ///
283 /// ```
284 /// use reinhardt_di::{Injected, Injectable};
285 ///
286 /// # #[derive(Clone, Default)]
287 /// # struct Config;
288 /// #
289 /// # #[async_trait::async_trait]
290 /// # impl Injectable for Config {
291 /// # async fn inject(ctx: &reinhardt_di::InjectionContext) -> reinhardt_di::DiResult<Self> {
292 /// # Ok(Config::default())
293 /// # }
294 /// # }
295 /// #
296 /// let injected = Injected::from_value(Config::default());
297 /// let metadata = injected.metadata();
298 /// assert!(!metadata.cached);
299 /// ```
300 pub fn metadata(&self) -> &InjectionMetadata {
301 &self.metadata
302 }
303
304 /// Attempt to unwrap the inner `Arc`, returning `T` if this is the only
305 /// strong reference. Returns `Err(Self)` if other references exist.
306 ///
307 /// This mirrors [`Arc::try_unwrap`] semantics. Unlike
308 /// [`into_inner`](Injected::into_inner), this method does **not** require
309 /// `T: Clone`.
310 ///
311 /// # Examples
312 ///
313 /// ```
314 /// use reinhardt_di::{Injected, Injectable};
315 ///
316 /// # #[derive(Clone, Debug, Default)]
317 /// # struct Config;
318 /// #
319 /// # #[async_trait::async_trait]
320 /// # impl Injectable for Config {
321 /// # async fn inject(ctx: &reinhardt_di::InjectionContext) -> reinhardt_di::DiResult<Self> {
322 /// # Ok(Config::default())
323 /// # }
324 /// # }
325 /// #
326 /// let injected = Injected::from_value(Config::default());
327 /// let config = injected.try_unwrap().unwrap();
328 /// ```
329 pub fn try_unwrap(self) -> Result<T, Self> {
330 match Arc::try_unwrap(self.inner) {
331 Ok(val) => Ok(val),
332 Err(arc) => Err(Self {
333 inner: arc,
334 metadata: self.metadata,
335 }),
336 }
337 }
338}
339
340#[allow(deprecated)]
341impl<T: Injectable + Clone> Injected<T> {
342 /// Extract inner value
343 ///
344 /// This method tries to unwrap the Arc. If the Arc has multiple strong references,
345 /// it clones the inner value instead. Requires `T: Clone`.
346 ///
347 /// # Examples
348 ///
349 /// ```
350 /// use reinhardt_di::{Injected, Injectable};
351 ///
352 /// # #[derive(Clone, Default)]
353 /// # struct Config;
354 /// #
355 /// # #[async_trait::async_trait]
356 /// # impl Injectable for Config {
357 /// # async fn inject(ctx: &reinhardt_di::InjectionContext) -> reinhardt_di::DiResult<Self> {
358 /// # Ok(Config::default())
359 /// # }
360 /// # }
361 /// #
362 /// let injected = Injected::from_value(Config::default());
363 /// let config = injected.into_inner();
364 /// ```
365 pub fn into_inner(self) -> T {
366 Arc::try_unwrap(self.inner).unwrap_or_else(|arc| (*arc).clone())
367 }
368}
369
370#[allow(deprecated)]
371impl<T: Injectable> Deref for Injected<T> {
372 type Target = T;
373
374 fn deref(&self) -> &Self::Target {
375 &self.inner
376 }
377}
378
379#[allow(deprecated)]
380impl<T: Injectable> Clone for Injected<T> {
381 fn clone(&self) -> Self {
382 Self {
383 inner: Arc::clone(&self.inner),
384 metadata: self.metadata,
385 }
386 }
387}
388
389#[allow(deprecated)]
390impl<T: Injectable> AsRef<T> for Injected<T> {
391 fn as_ref(&self) -> &T {
392 &self.inner
393 }
394}
395
396/// Optional injected dependency
397///
398/// Type alias for `Option<Injected<T>>`, used for optional dependencies.
399///
400/// # Critical Constraint
401///
402/// When using `#[inject]` attribute:
403/// - `#[inject(optional = true)]` → **MUST** use `OptionalInjected<T>` type
404/// - `#[inject(optional = false)]` or `#[inject]` → **MUST** use `Injected<T>` type
405/// - Type/attribute mismatches will cause compile errors
406///
407/// # Examples
408///
409/// ```
410/// use reinhardt_di::{Injected, OptionalInjected};
411///
412/// // ✅ Correct: optional = true with OptionalInjected<T>
413/// // #[get("/data", use_inject = true)]
414/// // async fn handler(
415/// // #[inject(optional = true)] cache: OptionalInjected<RedisCache>,
416/// // ) -> Result<String> {
417/// // if let Some(cache) = cache {
418/// // Ok(cache.get("data").await?)
419/// // } else {
420/// // Ok("No cache available".to_string())
421/// // }
422/// // }
423///
424/// // ✅ Correct: no optional (default false) with Injected<T>
425/// // #[get("/users", use_inject = true)]
426/// // async fn list_users(
427/// // #[inject] db: Injected<Database>,
428/// // ) -> Result<String> {
429/// // Ok(db.query("SELECT * FROM users").await?)
430/// // }
431///
432/// // ❌ Error: optional = true but type is Injected<T>
433/// // #[get("/bad", use_inject = true)]
434/// // async fn bad_handler(
435/// // #[inject(optional = true)] cache: Injected<RedisCache>,
436/// // // ^^^^^^^^^^^^^^^^^ Error!
437/// // ) -> Result<String> { ... }
438///
439/// // ❌ Error: optional = false but type is OptionalInjected<T>
440/// // #[get("/bad2", use_inject = true)]
441/// // async fn bad_handler2(
442/// // #[inject(optional = false)] db: OptionalInjected<Database>,
443/// // // ^^^^^^^^^^^^^^^^^^^^^^^^^ Error!
444/// // ) -> Result<String> { ... }
445/// ```
446#[deprecated(
447 since = "0.1.0-rc.16",
448 note = "use `Option<Depends<T>>` instead. `OptionalInjected<T>` will be removed in a future version."
449)]
450#[allow(deprecated)]
451pub type OptionalInjected<T> = Option<Injected<T>>;
452
453#[cfg(test)]
454#[allow(deprecated)]
455mod tests {
456 use super::*;
457 use crate::SingletonScope;
458
459 #[derive(Clone, Default, Debug)]
460 struct TestConfig {
461 value: String,
462 }
463
464 #[async_trait::async_trait]
465 impl Injectable for TestConfig {
466 async fn inject(_ctx: &InjectionContext) -> DiResult<Self> {
467 Ok(TestConfig {
468 value: "test".to_string(),
469 })
470 }
471 }
472
473 #[tokio::test]
474 async fn test_injected_from_value() {
475 let config = TestConfig {
476 value: "custom".to_string(),
477 };
478 let injected = Injected::from_value(config);
479 assert_eq!(injected.value, "custom");
480 }
481
482 #[tokio::test]
483 async fn test_injected_into_inner() {
484 let config = TestConfig {
485 value: "test".to_string(),
486 };
487 let injected = Injected::from_value(config);
488 let extracted = injected.into_inner();
489 assert_eq!(extracted.value, "test");
490 }
491
492 #[tokio::test]
493 async fn test_injected_clone() {
494 let config = TestConfig {
495 value: "test".to_string(),
496 };
497 let injected1 = Injected::from_value(config);
498 let injected2 = injected1.clone();
499
500 assert_eq!(injected1.value, "test");
501 assert_eq!(injected2.value, "test");
502 }
503
504 #[tokio::test]
505 async fn test_injected_deref() {
506 let config = TestConfig {
507 value: "test".to_string(),
508 };
509 let injected = Injected::from_value(config);
510
511 // Can be accessed directly via Deref
512 assert_eq!(injected.value, "test");
513 }
514
515 #[tokio::test]
516 async fn test_injected_metadata() {
517 let config = TestConfig {
518 value: "test".to_string(),
519 };
520 let injected = Injected::from_value(config);
521
522 let metadata = injected.metadata();
523 assert_eq!(metadata.scope, DependencyScope::Request);
524 assert!(!metadata.cached);
525 }
526
527 #[tokio::test]
528 async fn test_optional_injected_some() {
529 let config = TestConfig {
530 value: "test".to_string(),
531 };
532 let optional: OptionalInjected<TestConfig> = Some(Injected::from_value(config));
533
534 assert!(optional.is_some());
535 if let Some(injected) = optional {
536 assert_eq!(injected.value, "test");
537 }
538 }
539
540 #[tokio::test]
541 async fn test_optional_injected_none() {
542 let optional: OptionalInjected<TestConfig> = None;
543 assert!(optional.is_none());
544 }
545
546 // Additional dependency scope tests
547
548 #[test]
549 fn test_dependency_scope_equality() {
550 assert_eq!(DependencyScope::Request, DependencyScope::Request);
551 assert_eq!(DependencyScope::Singleton, DependencyScope::Singleton);
552 assert_ne!(DependencyScope::Request, DependencyScope::Singleton);
553 }
554
555 #[test]
556 fn test_dependency_scope_debug() {
557 let request = DependencyScope::Request;
558 let singleton = DependencyScope::Singleton;
559
560 let request_debug = format!("{:?}", request);
561 let singleton_debug = format!("{:?}", singleton);
562
563 assert!(request_debug.contains("Request"));
564 assert!(singleton_debug.contains("Singleton"));
565 }
566
567 #[test]
568 fn test_dependency_scope_clone() {
569 let request = DependencyScope::Request;
570 let cloned = request;
571
572 assert_eq!(request, cloned);
573 }
574
575 #[test]
576 fn test_injection_metadata_debug() {
577 let metadata = InjectionMetadata {
578 scope: DependencyScope::Request,
579 cached: true,
580 };
581
582 let debug_str = format!("{:?}", metadata);
583
584 assert!(debug_str.contains("InjectionMetadata"));
585 assert!(debug_str.contains("Request"));
586 assert!(debug_str.contains("true"));
587 }
588
589 #[test]
590 fn test_injection_metadata_clone() {
591 let metadata = InjectionMetadata {
592 scope: DependencyScope::Singleton,
593 cached: false,
594 };
595
596 let cloned = metadata;
597
598 assert_eq!(cloned.scope, DependencyScope::Singleton);
599 assert!(!cloned.cached);
600 }
601
602 #[test]
603 fn test_injection_metadata_copy() {
604 let metadata = InjectionMetadata {
605 scope: DependencyScope::Request,
606 cached: true,
607 };
608
609 // InjectionMetadata derives Copy
610 fn takes_copy<T: Copy>(_: T) {}
611 takes_copy(metadata);
612
613 // Original is still valid after copy
614 assert_eq!(metadata.scope, DependencyScope::Request);
615 assert!(metadata.cached);
616 }
617
618 #[tokio::test]
619 async fn test_injected_as_arc() {
620 let config = TestConfig {
621 value: "arc_test".to_string(),
622 };
623 let injected = Injected::from_value(config);
624
625 let arc = injected.as_arc();
626
627 // Arc reference provides access to inner value
628 assert_eq!(arc.value, "arc_test");
629
630 // Arc strong count should be 1 (only one reference)
631 assert_eq!(Arc::strong_count(arc), 1);
632 }
633
634 #[tokio::test]
635 async fn test_injected_as_ref() {
636 let config = TestConfig {
637 value: "ref_test".to_string(),
638 };
639 let injected = Injected::from_value(config);
640
641 // AsRef trait implementation
642 let reference: &TestConfig = injected.as_ref();
643 assert_eq!(reference.value, "ref_test");
644 }
645
646 #[tokio::test]
647 async fn test_injected_debug() {
648 let config = TestConfig {
649 value: "debug_test".to_string(),
650 };
651 let injected = Injected::from_value(config);
652
653 let debug_str = format!("{:?}", injected);
654
655 assert!(debug_str.contains("Injected"));
656 }
657
658 #[tokio::test]
659 async fn test_injected_resolve_with_context() {
660 let singleton_scope = Arc::new(SingletonScope::new());
661 let ctx = InjectionContext::builder(singleton_scope).build();
662
663 let config = Injected::<TestConfig>::resolve(&ctx).await.unwrap();
664
665 assert_eq!(config.value, "test");
666 assert!(config.metadata().cached);
667 assert_eq!(config.metadata().scope, DependencyScope::Request);
668 }
669
670 #[tokio::test]
671 async fn test_injected_resolve_uncached_with_context() {
672 let singleton_scope = Arc::new(SingletonScope::new());
673 let ctx = InjectionContext::builder(singleton_scope).build();
674
675 let config = Injected::<TestConfig>::resolve_uncached(&ctx)
676 .await
677 .unwrap();
678
679 assert_eq!(config.value, "test");
680 assert!(!config.metadata().cached);
681 }
682
683 #[tokio::test]
684 async fn test_injected_clone_shares_arc() {
685 let config = TestConfig {
686 value: "shared".to_string(),
687 };
688 let injected1 = Injected::from_value(config);
689 let injected2 = injected1.clone();
690
691 // Both should share the same Arc
692 assert_eq!(Arc::strong_count(injected1.as_arc()), 2);
693 assert_eq!(Arc::strong_count(injected2.as_arc()), 2);
694
695 // Both point to the same data
696 assert!(Arc::ptr_eq(injected1.as_arc(), injected2.as_arc()));
697 }
698
699 #[tokio::test]
700 async fn test_injected_metadata_preserved_on_clone() {
701 let config = TestConfig {
702 value: "metadata".to_string(),
703 };
704 let injected1 = Injected::from_value(config);
705 let injected2 = injected1.clone();
706
707 // Metadata should be identical
708 assert_eq!(injected1.metadata().scope, injected2.metadata().scope);
709 assert_eq!(injected1.metadata().cached, injected2.metadata().cached);
710 }
711
712 #[tokio::test]
713 async fn test_injected_into_inner_with_single_reference() {
714 let config = TestConfig {
715 value: "single".to_string(),
716 };
717 let injected = Injected::from_value(config);
718
719 // With single reference, Arc::try_unwrap succeeds
720 let inner = injected.into_inner();
721 assert_eq!(inner.value, "single");
722 }
723
724 #[tokio::test]
725 async fn test_injected_into_inner_with_multiple_references() {
726 let config = TestConfig {
727 value: "multiple".to_string(),
728 };
729 let injected1 = Injected::from_value(config);
730 let _injected2 = injected1.clone(); // Create second reference
731
732 // With multiple references, Arc::try_unwrap fails, falls back to clone
733 let inner = injected1.into_inner();
734 assert_eq!(inner.value, "multiple");
735 }
736
737 /// `try_unwrap()` succeeds when there is only one strong reference.
738 #[tokio::test]
739 async fn test_injected_try_unwrap_success() {
740 // Arrange
741 let config = TestConfig {
742 value: "owned".to_string(),
743 };
744 let injected = Injected::from_value(config);
745
746 // Act
747 let result = injected.try_unwrap();
748
749 // Assert
750 assert!(result.is_ok());
751 assert_eq!(result.unwrap().value, "owned");
752 }
753
754 /// `try_unwrap()` returns `Err(Self)` when multiple references exist.
755 #[tokio::test]
756 async fn test_injected_try_unwrap_err_multiple_refs() {
757 // Arrange
758 let config = TestConfig {
759 value: "shared".to_string(),
760 };
761 let injected = Injected::from_value(config);
762 let _clone = injected.clone();
763
764 // Act
765 let result = injected.try_unwrap();
766
767 // Assert
768 let returned = result.unwrap_err();
769 assert_eq!(returned.value, "shared");
770 assert_eq!(returned.metadata().scope, DependencyScope::Request);
771 }
772}