dependency_injector/
scope.rs

1//! Scoped container support
2//!
3//! Provides utilities for working with scoped service lifetimes.
4
5use crate::{Container, Injectable, Result};
6use std::sync::Arc;
7use std::sync::atomic::{AtomicU64, Ordering};
8
9#[cfg(feature = "logging")]
10use tracing::debug;
11
12/// Unique scope identifier.
13///
14/// Each scope gets a unique ID for tracking and debugging.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct Scope(u64);
17
18impl Scope {
19    /// Generate a new unique scope ID.
20    #[inline]
21    pub fn new() -> Self {
22        static COUNTER: AtomicU64 = AtomicU64::new(1);
23        Self(COUNTER.fetch_add(1, Ordering::Relaxed))
24    }
25
26    /// Get the raw ID value.
27    #[inline]
28    pub fn id(&self) -> u64 {
29        self.0
30    }
31}
32
33impl Default for Scope {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl std::fmt::Display for Scope {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        write!(f, "scope-{}", self.0)
42    }
43}
44
45/// A container with an associated scope identifier.
46///
47/// Useful for request-scoped or session-scoped services where you need
48/// to track the scope identity.
49///
50/// # Examples
51///
52/// ```rust
53/// use dependency_injector::{Container, ScopedContainer};
54///
55/// #[derive(Clone)]
56/// struct RequestContext {
57///     request_id: String,
58/// }
59///
60/// let root = Container::new();
61///
62/// // Create a request-scoped container
63/// let request = ScopedContainer::from_parent(&root);
64/// request.singleton(RequestContext {
65///     request_id: "req-123".to_string(),
66/// });
67///
68/// let ctx = request.get::<RequestContext>().unwrap();
69/// assert_eq!(ctx.request_id, "req-123");
70/// ```
71pub struct ScopedContainer {
72    /// The underlying container
73    container: Container,
74    /// Scope identifier
75    scope: Scope,
76}
77
78impl ScopedContainer {
79    /// Create a new scoped container with no parent.
80    #[inline]
81    pub fn new() -> Self {
82        let scope = Scope::new();
83
84        #[cfg(feature = "logging")]
85        debug!(
86            target: "dependency_injector",
87            scope_id = scope.id(),
88            "Creating new root ScopedContainer"
89        );
90
91        Self {
92            container: Container::new(),
93            scope,
94        }
95    }
96
97    /// Create a scoped container from a parent container.
98    #[inline]
99    pub fn from_parent(parent: &Container) -> Self {
100        let scope = Scope::new();
101
102        #[cfg(feature = "logging")]
103        debug!(
104            target: "dependency_injector",
105            scope_id = scope.id(),
106            parent_depth = parent.depth(),
107            "Creating ScopedContainer from parent Container"
108        );
109
110        Self {
111            container: parent.scope(),
112            scope,
113        }
114    }
115
116    /// Create a scoped container from another scoped container.
117    #[inline]
118    pub fn from_scope(parent: &ScopedContainer) -> Self {
119        let scope = Scope::new();
120
121        #[cfg(feature = "logging")]
122        debug!(
123            target: "dependency_injector",
124            scope_id = scope.id(),
125            parent_scope_id = parent.scope.id(),
126            "Creating child ScopedContainer from parent ScopedContainer"
127        );
128
129        Self {
130            container: parent.container.scope(),
131            scope,
132        }
133    }
134
135    /// Get the scope identifier.
136    #[inline]
137    pub fn scope(&self) -> Scope {
138        self.scope
139    }
140
141    /// Register a singleton in this scope.
142    #[inline]
143    pub fn singleton<T: Injectable>(&self, instance: T) {
144        self.container.singleton(instance);
145    }
146
147    /// Register a lazy singleton in this scope.
148    #[inline]
149    pub fn lazy<T: Injectable, F>(&self, factory: F)
150    where
151        F: Fn() -> T + Send + Sync + 'static,
152    {
153        self.container.lazy(factory);
154    }
155
156    /// Register a transient service in this scope.
157    #[inline]
158    pub fn transient<T: Injectable, F>(&self, factory: F)
159    where
160        F: Fn() -> T + Send + Sync + 'static,
161    {
162        self.container.transient(factory);
163    }
164
165    /// Alias for singleton - register an instance.
166    #[inline]
167    pub fn register<T: Injectable>(&self, instance: T) {
168        self.container.register(instance);
169    }
170
171    /// Register using a factory.
172    #[inline]
173    pub fn register_factory<T: Injectable, F>(&self, factory: F)
174    where
175        F: Fn() -> T + Send + Sync + 'static,
176    {
177        self.container.register_factory(factory);
178    }
179
180    /// Resolve a service from this scope or parent scopes.
181    #[inline]
182    pub fn get<T: Injectable>(&self) -> Result<Arc<T>> {
183        self.container.get::<T>()
184    }
185
186    /// Alias for get.
187    #[inline]
188    pub fn resolve<T: Injectable>(&self) -> Result<Arc<T>> {
189        self.get::<T>()
190    }
191
192    /// Try to resolve a service, returning None if not found.
193    #[inline]
194    pub fn try_get<T: Injectable>(&self) -> Option<Arc<T>> {
195        self.container.try_get::<T>()
196    }
197
198    /// Alias for try_get.
199    #[inline]
200    pub fn try_resolve<T: Injectable>(&self) -> Option<Arc<T>> {
201        self.try_get::<T>()
202    }
203
204    /// Check if a service exists in this scope or parent scopes.
205    #[inline]
206    pub fn contains<T: Injectable>(&self) -> bool {
207        self.container.contains::<T>()
208    }
209
210    /// Alias for contains.
211    #[inline]
212    pub fn has<T: Injectable>(&self) -> bool {
213        self.contains::<T>()
214    }
215
216    /// Get the underlying container.
217    #[inline]
218    pub fn container(&self) -> &Container {
219        &self.container
220    }
221
222    /// Get the underlying container mutably.
223    #[inline]
224    pub fn container_mut(&mut self) -> &mut Container {
225        &mut self.container
226    }
227
228    /// Get the scope depth.
229    #[inline]
230    pub fn depth(&self) -> u32 {
231        self.container.depth()
232    }
233}
234
235impl Default for ScopedContainer {
236    fn default() -> Self {
237        Self::new()
238    }
239}
240
241impl std::fmt::Debug for ScopedContainer {
242    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243        f.debug_struct("ScopedContainer")
244            .field("scope", &self.scope)
245            .field("container", &self.container)
246            .finish()
247    }
248}
249
250/// Builder for creating scoped containers with pre-configured services.
251///
252/// Useful when you have a standard set of services to register per-scope.
253///
254/// # Examples
255///
256/// ```rust
257/// use dependency_injector::{Container, ScopeBuilder};
258/// use std::sync::atomic::{AtomicU64, Ordering};
259///
260/// static COUNTER: AtomicU64 = AtomicU64::new(0);
261///
262/// #[derive(Clone)]
263/// struct RequestId(u64);
264///
265/// let root = Container::new();
266///
267/// let builder = ScopeBuilder::new()
268///     .with_transient(|| RequestId(COUNTER.fetch_add(1, Ordering::SeqCst)));
269///
270/// let scope1 = builder.build(&root);
271/// let scope2 = builder.build(&root);
272///
273/// // Each scope gets its own services
274/// ```
275pub struct ScopeBuilder {
276    #[allow(clippy::type_complexity)]
277    factories: Vec<Box<dyn Fn(&Container) + Send + Sync>>,
278}
279
280impl ScopeBuilder {
281    /// Create a new scope builder.
282    #[inline]
283    pub fn new() -> Self {
284        Self {
285            factories: Vec::new(),
286        }
287    }
288
289    /// Add a singleton factory to run in each scope.
290    pub fn with_singleton<T, F>(mut self, factory: F) -> Self
291    where
292        T: Injectable + Clone,
293        F: Fn() -> T + Send + Sync + 'static,
294    {
295        self.factories.push(Box::new(move |container| {
296            container.singleton(factory());
297        }));
298        self
299    }
300
301    /// Add a lazy singleton factory.
302    pub fn with_lazy<T, F>(mut self, factory: F) -> Self
303    where
304        T: Injectable,
305        F: Fn() -> T + Send + Sync + Clone + 'static,
306    {
307        self.factories.push(Box::new(move |container| {
308            let f = factory.clone();
309            container.lazy(f);
310        }));
311        self
312    }
313
314    /// Add a transient factory.
315    pub fn with_transient<T, F>(mut self, factory: F) -> Self
316    where
317        T: Injectable,
318        F: Fn() -> T + Send + Sync + Clone + 'static,
319    {
320        self.factories.push(Box::new(move |container| {
321            let f = factory.clone();
322            container.transient(f);
323        }));
324        self
325    }
326
327    /// Build a scoped container with all registered services.
328    pub fn build(&self, parent: &Container) -> ScopedContainer {
329        let scoped = ScopedContainer::from_parent(parent);
330        for factory in &self.factories {
331            factory(&scoped.container);
332        }
333        scoped
334    }
335}
336
337impl Default for ScopeBuilder {
338    fn default() -> Self {
339        Self::new()
340    }
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    #[derive(Clone)]
348    struct GlobalService;
349
350    #[derive(Clone)]
351    struct RequestService {
352        id: String,
353    }
354
355    #[test]
356    fn test_scoped_container() {
357        let root = Container::new();
358        root.singleton(GlobalService);
359
360        let scoped = ScopedContainer::from_parent(&root);
361        scoped.singleton(RequestService { id: "req-1".into() });
362
363        // Can access both
364        assert!(scoped.contains::<GlobalService>());
365        assert!(scoped.contains::<RequestService>());
366
367        // Root doesn't have scoped service
368        assert!(!root.contains::<RequestService>());
369    }
370
371    #[test]
372    fn test_scope_ids_unique() {
373        let s1 = Scope::new();
374        let s2 = Scope::new();
375        let s3 = Scope::new();
376
377        assert_ne!(s1.id(), s2.id());
378        assert_ne!(s2.id(), s3.id());
379    }
380
381    #[test]
382    fn test_scope_builder() {
383        let root = Container::new();
384        root.singleton(GlobalService);
385
386        let builder = ScopeBuilder::new().with_singleton(|| RequestService { id: "built".into() });
387
388        let scoped = builder.build(&root);
389
390        assert!(scoped.contains::<GlobalService>());
391        assert!(scoped.contains::<RequestService>());
392
393        let req = scoped.get::<RequestService>().unwrap();
394        assert_eq!(req.id, "built");
395    }
396
397    #[test]
398    fn test_scope_display() {
399        let scope = Scope::new();
400        let display = format!("{}", scope);
401        assert!(display.starts_with("scope-"));
402    }
403}