springtime_di/
scope.rs

1//! Component instances are contained in [Scope]s - containers which decide when to reuse or create
2//! an instance. There's a global one for singletons, but there also can be other, specialized ones.
3//! Some can be simple, like [PrototypeScope], while others can be quite complex and depend on
4//! external factors, e.g. tying the lifetime of instances to web sessions.
5//!
6//! Note: scope resolution happens at component instantiation time, which can lead to unexpected
7//! consequences if incompatible scopes are mixed together, e.g. a [singleton](SINGLETON) component
8//! can depend on a [prototype](PROTOTYPE) one. In such a case when creating the singleton, a new
9//! instance of the dependency will be created, since it's a prototype, but then that single
10//! instance will live as long as the singleton lives.
11
12use crate::component_registry::ComponentDefinition;
13use crate::instance_provider::ComponentInstanceAnyPtr;
14#[cfg(test)]
15use mockall::automock;
16use rustc_hash::FxHashMap;
17use std::any::TypeId;
18
19#[cfg(not(feature = "threadsafe"))]
20pub type ScopePtr = Box<dyn Scope>;
21#[cfg(feature = "threadsafe")]
22pub type ScopePtr = Box<dyn Scope + Send + Sync>;
23
24/// Name of the [SingletonScope].
25pub const SINGLETON: &str = "SINGLETON";
26
27/// Name of the [PrototypeScope].
28pub const PROTOTYPE: &str = "PROTOTYPE";
29
30/// A scope containing component instances. See module documentation for information on scopes.
31#[cfg_attr(test, automock)]
32pub trait Scope {
33    /// Gets an instance requested for the given definition, if available in this scope.
34    fn instance(&self, definition: &ComponentDefinition) -> Option<ComponentInstanceAnyPtr>;
35
36    /// Stores a given instance in the scope. The scope might not support storing instances and
37    /// ignore it.
38    fn store_instance(
39        &mut self,
40        definition: &ComponentDefinition,
41        instance: ComponentInstanceAnyPtr,
42    );
43}
44
45/// Scope for instances shared between components. Stateless components are good candidates to be
46/// stored in the singleton scope.
47#[derive(Default)]
48pub struct SingletonScope {
49    instances: FxHashMap<TypeId, ComponentInstanceAnyPtr>,
50}
51
52impl Scope for SingletonScope {
53    #[inline]
54    fn instance(&self, definition: &ComponentDefinition) -> Option<ComponentInstanceAnyPtr> {
55        self.instances.get(&definition.resolved_type_id).cloned()
56    }
57
58    #[inline]
59    fn store_instance(
60        &mut self,
61        definition: &ComponentDefinition,
62        instance: ComponentInstanceAnyPtr,
63    ) {
64        self.instances.insert(definition.resolved_type_id, instance);
65    }
66}
67
68/// A scope which creates a new instance of a given component on each request. Stateful components
69/// usually should be stored in a prototype scope.
70#[derive(Default, Copy, Clone, Eq, PartialEq)]
71pub struct PrototypeScope;
72
73impl Scope for PrototypeScope {
74    #[inline]
75    fn instance(&self, _definition: &ComponentDefinition) -> Option<ComponentInstanceAnyPtr> {
76        None
77    }
78
79    #[inline]
80    fn store_instance(
81        &mut self,
82        _definition: &ComponentDefinition,
83        _instance: ComponentInstanceAnyPtr,
84    ) {
85    }
86}
87
88/// Factory for custom [Scope]s.
89#[cfg_attr(test, automock)]
90pub trait ScopeFactory {
91    fn create_scope(&self) -> ScopePtr;
92}
93
94#[derive(Copy, Clone, Eq, PartialEq, Default)]
95pub struct SingletonScopeFactory;
96
97impl ScopeFactory for SingletonScopeFactory {
98    fn create_scope(&self) -> ScopePtr {
99        Box::<SingletonScope>::default()
100    }
101}
102
103#[derive(Copy, Clone, Eq, PartialEq, Default)]
104pub struct PrototypeScopeFactory;
105
106impl ScopeFactory for PrototypeScopeFactory {
107    fn create_scope(&self) -> ScopePtr {
108        Box::<PrototypeScope>::default()
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    #[cfg(not(feature = "async"))]
115    mod sync {
116        use crate::component_registry::ComponentDefinition;
117        use crate::instance_provider::ComponentInstanceProviderError;
118        use crate::instance_provider::{
119            ComponentInstanceAnyPtr, ComponentInstanceProvider, ComponentInstancePtr,
120        };
121        use crate::scope::{PrototypeScopeFactory, ScopeFactory, SingletonScopeFactory};
122        use std::any::{type_name, Any, TypeId};
123
124        fn test_constructor(
125            _instance_provider: &mut dyn ComponentInstanceProvider,
126        ) -> Result<ComponentInstanceAnyPtr, ComponentInstanceProviderError> {
127            Err(ComponentInstanceProviderError::IncompatibleComponent {
128                type_id: TypeId::of::<i8>(),
129                type_name: type_name::<i8>().to_string(),
130            })
131        }
132
133        fn test_cast(
134            instance: ComponentInstanceAnyPtr,
135        ) -> Result<Box<dyn Any>, ComponentInstanceAnyPtr> {
136            Err(instance)
137        }
138
139        fn create_definition() -> ComponentDefinition {
140            ComponentDefinition {
141                names: Default::default(),
142                is_primary: false,
143                scope: "".to_string(),
144                resolved_type_id: TypeId::of::<u8>(),
145                resolved_type_name: type_name::<u8>().to_string(),
146                constructor: test_constructor,
147                cast: test_cast,
148            }
149        }
150
151        #[test]
152        fn should_support_singletons() {
153            let definition = create_definition();
154            let factory = SingletonScopeFactory;
155            let mut scope = factory.create_scope();
156
157            let instance = ComponentInstancePtr::new(0) as ComponentInstanceAnyPtr;
158            scope.store_instance(&definition, instance.clone());
159
160            assert!(scope.instance(&definition).is_some());
161        }
162
163        #[test]
164        fn should_support_prototypes() {
165            let definition = create_definition();
166            let factory = PrototypeScopeFactory;
167            let mut scope = factory.create_scope();
168
169            let instance = ComponentInstancePtr::new(0) as ComponentInstanceAnyPtr;
170            scope.store_instance(&definition, instance.clone());
171
172            assert!(scope.instance(&definition).is_none());
173        }
174    }
175}