springtime_di/
instance_provider.rs

1//! The core functionality of creating and managing [Component](crate::component::Component)
2//! instances.
3
4use crate::component::Injectable;
5#[cfg(feature = "async")]
6use futures::future::BoxFuture;
7#[cfg(feature = "async")]
8use futures::FutureExt;
9use itertools::Itertools;
10#[cfg(test)]
11use mockall::automock;
12use std::any::{type_name, Any, TypeId};
13use std::error::Error;
14#[cfg(not(feature = "threadsafe"))]
15use std::rc::Rc;
16#[cfg(feature = "threadsafe")]
17use std::sync::Arc;
18use thiserror::Error;
19
20#[cfg(not(feature = "threadsafe"))]
21pub type ErrorPtr = Rc<dyn Error>;
22#[cfg(feature = "threadsafe")]
23pub type ErrorPtr = Arc<dyn Error + Send + Sync>;
24
25/// Errors related to creating and managing components.
26#[derive(Error, Debug, Clone)]
27pub enum ComponentInstanceProviderError {
28    /// Primary instance of a given component is not specified, if many components exist for a given
29    /// type, or not component registered at all.
30    #[error("Cannot find a primary instance for component '{type_id:?}/{type_name:?}' - either none or multiple exists without a primary marker.")]
31    NoPrimaryInstance {
32        type_id: TypeId,
33        type_name: Option<String>,
34    },
35    /// Tired to case one type to another, incompatible one.
36    #[error("Tried to downcast component to incompatible type: {type_id:?}/{type_name}")]
37    IncompatibleComponent { type_id: TypeId, type_name: String },
38    /// Cannot find component with given name.
39    #[error("Cannot find named component: {0}")]
40    NoNamedInstance(String),
41    /// Component registered for unknown scope - possibly missing associated
42    /// [ScopeFactory](crate::scope::ScopeFactory).
43    #[error("Unrecognized scope: {0}")]
44    UnrecognizedScope(String),
45    #[error("Detected dependency cycle for: {type_id:?}/{type_name:?}")]
46    /// Found a cycle when creating given type.
47    DependencyCycle {
48        type_id: TypeId,
49        type_name: Option<String>,
50    },
51    /// Custom constructor returned an error.
52    #[error("Error in component constructor: {0}")]
53    ConstructorError(#[source] ErrorPtr),
54}
55
56#[cfg(not(feature = "threadsafe"))]
57pub type ComponentInstancePtr<T> = Rc<T>;
58#[cfg(feature = "threadsafe")]
59pub type ComponentInstancePtr<T> = Arc<T>;
60
61#[cfg(not(feature = "threadsafe"))]
62pub type ComponentInstanceAnyPtr = ComponentInstancePtr<dyn Any + 'static>;
63#[cfg(feature = "threadsafe")]
64pub type ComponentInstanceAnyPtr = ComponentInstancePtr<dyn Any + Send + Sync + 'static>;
65
66/// (Usually generated) cast function which consumes given type-erased instance pointer and casts it
67/// to the desired [`ComponentInstancePtr<T>`]. The result is then returned as type-erased `Box` which
68/// is then converted back to [`ComponentInstancePtr<T>`]. Such shenanigans are needed to be able to
69/// convert between two `dyn Traits`.
70pub type CastFunction =
71    fn(instance: ComponentInstanceAnyPtr) -> Result<Box<dyn Any>, ComponentInstanceAnyPtr>;
72
73/// Generic provider for component instances.
74#[cfg(feature = "async")]
75#[cfg_attr(test, automock)]
76pub trait ComponentInstanceProvider {
77    /// Tries to return a primary instance of a given component. A primary component is either the
78    /// only one registered or one marked as primary.
79    fn primary_instance(
80        &mut self,
81        type_id: TypeId,
82    ) -> BoxFuture<
83        '_,
84        Result<(ComponentInstanceAnyPtr, CastFunction), ComponentInstanceProviderError>,
85    >;
86
87    /// Tries to instantiate and return all registered components for given type, stopping on first
88    /// error. Be aware this might be an expensive operation if the number of registered components
89    /// is high.
90    fn instances(
91        &mut self,
92        type_id: TypeId,
93    ) -> BoxFuture<
94        '_,
95        Result<Vec<(ComponentInstanceAnyPtr, CastFunction)>, ComponentInstanceProviderError>,
96    >;
97
98    /// Tries to return an instance with the given name and type.
99    fn instance_by_name(
100        &mut self,
101        name: &str,
102        type_id: TypeId,
103    ) -> BoxFuture<
104        '_,
105        Result<(ComponentInstanceAnyPtr, CastFunction), ComponentInstanceProviderError>,
106    >;
107}
108
109#[cfg(not(feature = "async"))]
110#[cfg_attr(test, automock)]
111pub trait ComponentInstanceProvider {
112    /// Tries to return a primary instance of a given component. A primary component is either the
113    /// only one registered or one marked as primary.
114    fn primary_instance(
115        &mut self,
116        type_id: TypeId,
117    ) -> Result<(ComponentInstanceAnyPtr, CastFunction), ComponentInstanceProviderError>;
118
119    /// Tries to instantiate and return all registered components for given type, stopping on first
120    /// error. Be aware this might be an expensive operation if the number of registered components
121    /// is high.
122    fn instances(
123        &mut self,
124        type_id: TypeId,
125    ) -> Result<Vec<(ComponentInstanceAnyPtr, CastFunction)>, ComponentInstanceProviderError>;
126
127    /// Tries to return an instance with the given name and type.
128    fn instance_by_name(
129        &mut self,
130        name: &str,
131        type_id: TypeId,
132    ) -> Result<(ComponentInstanceAnyPtr, CastFunction), ComponentInstanceProviderError>;
133}
134
135/// Helper trait for [ComponentInstanceProvider] providing strongly-typed access.
136#[cfg(feature = "async")]
137pub trait TypedComponentInstanceProvider {
138    /// Typesafe version of [ComponentInstanceProvider::primary_instance].
139    fn primary_instance_typed<T: Injectable + ?Sized>(
140        &mut self,
141    ) -> BoxFuture<'_, Result<ComponentInstancePtr<T>, ComponentInstanceProviderError>>;
142
143    /// Tries to get an instance like [TypedComponentInstanceProvider::primary_instance_typed] does,
144    /// but returns `None` on missing instance.
145    fn primary_instance_option<T: Injectable + ?Sized>(
146        &mut self,
147    ) -> BoxFuture<'_, Result<Option<ComponentInstancePtr<T>>, ComponentInstanceProviderError>>;
148
149    /// Typesafe version of [ComponentInstanceProvider::instances].
150    fn instances_typed<T: Injectable + ?Sized>(
151        &mut self,
152    ) -> BoxFuture<'_, Result<Vec<ComponentInstancePtr<T>>, ComponentInstanceProviderError>>;
153
154    /// Typesafe version of [ComponentInstanceProvider::instance_by_name].
155    fn instance_by_name_typed<T: Injectable + ?Sized>(
156        &mut self,
157        name: &str,
158    ) -> BoxFuture<'_, Result<ComponentInstancePtr<T>, ComponentInstanceProviderError>>;
159
160    /// Tries to get an instance like [TypedComponentInstanceProvider::instance_by_name_typed] does,
161    /// but returns `None` on missing instance.
162    fn instance_by_name_option<T: Injectable + ?Sized>(
163        &mut self,
164        name: &str,
165    ) -> BoxFuture<'_, Result<Option<ComponentInstancePtr<T>>, ComponentInstanceProviderError>>;
166}
167
168/// Helper trait for [ComponentInstanceProvider] providing strongly-typed access.
169#[cfg(not(feature = "async"))]
170pub trait TypedComponentInstanceProvider {
171    /// Typesafe version of [ComponentInstanceProvider::primary_instance].
172    fn primary_instance_typed<T: Injectable + ?Sized>(
173        &mut self,
174    ) -> Result<ComponentInstancePtr<T>, ComponentInstanceProviderError>;
175
176    /// Tries to get an instance like [TypedComponentInstanceProvider::primary_instance_typed] does,
177    /// but returns `None` on missing instance.
178    fn primary_instance_option<T: Injectable + ?Sized>(
179        &mut self,
180    ) -> Result<Option<ComponentInstancePtr<T>>, ComponentInstanceProviderError>;
181
182    /// Typesafe version of [ComponentInstanceProvider::instances].
183    fn instances_typed<T: Injectable + ?Sized>(
184        &mut self,
185    ) -> Result<Vec<ComponentInstancePtr<T>>, ComponentInstanceProviderError>;
186
187    /// Typesafe version of [ComponentInstanceProvider::instance_by_name].
188    fn instance_by_name_typed<T: Injectable + ?Sized>(
189        &mut self,
190        name: &str,
191    ) -> Result<ComponentInstancePtr<T>, ComponentInstanceProviderError>;
192
193    /// Tries to get an instance like [TypedComponentInstanceProvider::instance_by_name_typed] does,
194    /// but returns `None` on missing instance.
195    fn instance_by_name_option<T: Injectable + ?Sized>(
196        &mut self,
197        name: &str,
198    ) -> Result<Option<ComponentInstancePtr<T>>, ComponentInstanceProviderError>;
199}
200
201//noinspection DuplicatedCode
202#[cfg(feature = "async")]
203impl<CIP: ComponentInstanceProvider + ?Sized + Sync + Send> TypedComponentInstanceProvider for CIP {
204    fn primary_instance_typed<T: Injectable + ?Sized>(
205        &mut self,
206    ) -> BoxFuture<'_, Result<ComponentInstancePtr<T>, ComponentInstanceProviderError>> {
207        async {
208            let type_id = TypeId::of::<T>();
209            self.primary_instance(type_id)
210                .await
211                .and_then(move |(p, cast)| cast_instance(p, cast, type_id))
212                .map_err(|error| enrich_error::<T>(error))
213        }
214        .boxed()
215    }
216
217    fn primary_instance_option<T: Injectable + ?Sized>(
218        &mut self,
219    ) -> BoxFuture<'_, Result<Option<ComponentInstancePtr<T>>, ComponentInstanceProviderError>>
220    {
221        async {
222            match self.primary_instance_typed::<T>().await {
223                Ok(ptr) => Ok(Some(ptr)),
224                Err(ComponentInstanceProviderError::NoPrimaryInstance { .. }) => Ok(None),
225                Err(error) => Err(enrich_error::<T>(error)),
226            }
227        }
228        .boxed()
229    }
230
231    fn instances_typed<T: Injectable + ?Sized>(
232        &mut self,
233    ) -> BoxFuture<'_, Result<Vec<ComponentInstancePtr<T>>, ComponentInstanceProviderError>> {
234        async {
235            let type_id = TypeId::of::<T>();
236            self.instances(type_id)
237                .await
238                .and_then(|instances| {
239                    instances
240                        .into_iter()
241                        .map(move |(p, cast)| cast_instance(p, cast, type_id))
242                        .try_collect()
243                })
244                .map_err(|error| enrich_error::<T>(error))
245        }
246        .boxed()
247    }
248
249    fn instance_by_name_typed<T: Injectable + ?Sized>(
250        &mut self,
251        name: &str,
252    ) -> BoxFuture<'_, Result<ComponentInstancePtr<T>, ComponentInstanceProviderError>> {
253        let name = name.to_string();
254        async move {
255            let type_id = TypeId::of::<T>();
256            self.instance_by_name(&name, type_id)
257                .await
258                .and_then(move |(p, cast)| cast_instance(p, cast, type_id))
259                .map_err(|error| enrich_error::<T>(error))
260        }
261        .boxed()
262    }
263
264    fn instance_by_name_option<T: Injectable + ?Sized>(
265        &mut self,
266        name: &str,
267    ) -> BoxFuture<'_, Result<Option<ComponentInstancePtr<T>>, ComponentInstanceProviderError>>
268    {
269        let name = name.to_string();
270        async move {
271            match self.instance_by_name_typed::<T>(&name).await {
272                Ok(ptr) => Ok(Some(ptr)),
273                Err(ComponentInstanceProviderError::NoPrimaryInstance { .. }) => Ok(None),
274                Err(error) => Err(enrich_error::<T>(error)),
275            }
276        }
277        .boxed()
278    }
279}
280
281//noinspection DuplicatedCode
282#[cfg(not(feature = "async"))]
283impl<CIP: ComponentInstanceProvider + ?Sized> TypedComponentInstanceProvider for CIP {
284    fn primary_instance_typed<T: Injectable + ?Sized>(
285        &mut self,
286    ) -> Result<ComponentInstancePtr<T>, ComponentInstanceProviderError> {
287        let type_id = TypeId::of::<T>();
288        self.primary_instance(type_id)
289            .and_then(move |(p, cast)| cast_instance(p, cast, type_id))
290            .map_err(|error| enrich_error::<T>(error))
291    }
292
293    fn primary_instance_option<T: Injectable + ?Sized>(
294        &mut self,
295    ) -> Result<Option<ComponentInstancePtr<T>>, ComponentInstanceProviderError> {
296        match self.primary_instance_typed::<T>() {
297            Ok(ptr) => Ok(Some(ptr)),
298            Err(ComponentInstanceProviderError::NoPrimaryInstance { .. }) => Ok(None),
299            Err(error) => Err(enrich_error::<T>(error)),
300        }
301    }
302
303    fn instances_typed<T: Injectable + ?Sized>(
304        &mut self,
305    ) -> Result<Vec<ComponentInstancePtr<T>>, ComponentInstanceProviderError> {
306        let type_id = TypeId::of::<T>();
307        self.instances(type_id)
308            .and_then(|instances| {
309                instances
310                    .into_iter()
311                    .map(move |(p, cast)| cast_instance(p, cast, type_id))
312                    .try_collect()
313            })
314            .map_err(|error| enrich_error::<T>(error))
315    }
316
317    fn instance_by_name_typed<T: Injectable + ?Sized>(
318        &mut self,
319        name: &str,
320    ) -> Result<ComponentInstancePtr<T>, ComponentInstanceProviderError> {
321        let type_id = TypeId::of::<T>();
322        self.instance_by_name(name, type_id)
323            .and_then(move |(p, cast)| cast_instance(p, cast, type_id))
324            .map_err(|error| enrich_error::<T>(error))
325    }
326
327    fn instance_by_name_option<T: Injectable + ?Sized>(
328        &mut self,
329        name: &str,
330    ) -> Result<Option<ComponentInstancePtr<T>>, ComponentInstanceProviderError> {
331        match self.instance_by_name_typed::<T>(name) {
332            Ok(ptr) => Ok(Some(ptr)),
333            Err(ComponentInstanceProviderError::NoPrimaryInstance { .. }) => Ok(None),
334            Err(error) => Err(enrich_error::<T>(error)),
335        }
336    }
337}
338
339fn enrich_error<T: ?Sized>(
340    error: ComponentInstanceProviderError,
341) -> ComponentInstanceProviderError {
342    match error {
343        ComponentInstanceProviderError::NoPrimaryInstance {
344            type_id,
345            type_name: None,
346        } => ComponentInstanceProviderError::NoPrimaryInstance {
347            type_id,
348            type_name: Some(type_name::<T>().to_string()),
349        },
350        ComponentInstanceProviderError::DependencyCycle {
351            type_id,
352            type_name: None,
353        } => ComponentInstanceProviderError::DependencyCycle {
354            type_id,
355            type_name: Some(type_name::<T>().to_string()),
356        },
357        _ => error,
358    }
359}
360
361fn cast_instance<T: Injectable + ?Sized>(
362    instance: ComponentInstanceAnyPtr,
363    cast: CastFunction,
364    type_id: TypeId,
365) -> Result<ComponentInstancePtr<T>, ComponentInstanceProviderError> {
366    debug_assert_eq!(type_id, TypeId::of::<T>());
367    cast(instance)
368        .map_err(|_| ComponentInstanceProviderError::IncompatibleComponent {
369            type_id,
370            type_name: type_name::<T>().to_string(),
371        })
372        .and_then(|p| {
373            p.downcast::<ComponentInstancePtr<T>>()
374                .map(|p| (*p).clone())
375                .map_err(|_| ComponentInstanceProviderError::IncompatibleComponent {
376                    type_id,
377                    type_name: type_name::<T>().to_string(),
378                })
379        })
380}
381
382#[cfg(test)]
383//noinspection DuplicatedCode
384mod tests {
385    #[cfg(not(feature = "async"))]
386    mod sync {
387        use crate::component::Injectable;
388        use crate::instance_provider::{
389            CastFunction, ComponentInstanceAnyPtr, ComponentInstancePtr,
390            MockComponentInstanceProvider, TypedComponentInstanceProvider,
391        };
392        use mockall::predicate::*;
393        use std::any::{Any, TypeId};
394
395        struct TestComponent;
396
397        impl Injectable for TestComponent {}
398
399        fn test_cast(
400            instance: ComponentInstanceAnyPtr,
401        ) -> Result<Box<dyn Any>, ComponentInstanceAnyPtr> {
402            instance
403                .downcast::<TestComponent>()
404                .map(|p| Box::new(p) as Box<dyn Any>)
405        }
406
407        #[test]
408        fn should_provide_primary_instance_typed() {
409            let mut instance_provider = MockComponentInstanceProvider::new();
410            instance_provider
411                .expect_primary_instance()
412                .with(eq(TypeId::of::<TestComponent>()))
413                .times(1)
414                .return_const(Ok((
415                    ComponentInstancePtr::new(TestComponent) as ComponentInstanceAnyPtr,
416                    test_cast as CastFunction,
417                )));
418
419            assert!(instance_provider
420                .primary_instance_typed::<TestComponent>()
421                .is_ok());
422        }
423
424        #[test]
425        fn should_provide_primary_instance_option() {
426            let mut instance_provider = MockComponentInstanceProvider::new();
427            instance_provider
428                .expect_primary_instance()
429                .with(eq(TypeId::of::<TestComponent>()))
430                .times(1)
431                .return_const(Ok((
432                    ComponentInstancePtr::new(TestComponent) as ComponentInstanceAnyPtr,
433                    test_cast as CastFunction,
434                )));
435
436            assert!(instance_provider
437                .primary_instance_option::<TestComponent>()
438                .unwrap()
439                .is_some());
440        }
441
442        #[test]
443        fn should_provide_instances_typed() {
444            let mut instance_provider = MockComponentInstanceProvider::new();
445            instance_provider
446                .expect_instances()
447                .with(eq(TypeId::of::<TestComponent>()))
448                .times(1)
449                .return_const(Ok(vec![(
450                    ComponentInstancePtr::new(TestComponent) as ComponentInstanceAnyPtr,
451                    test_cast as CastFunction,
452                )]));
453
454            assert!(!instance_provider
455                .instances_typed::<TestComponent>()
456                .unwrap()
457                .is_empty());
458        }
459
460        #[test]
461        fn should_provide_instance_by_name_typed() {
462            let name = "name";
463
464            let mut instance_provider = MockComponentInstanceProvider::new();
465            instance_provider
466                .expect_instance_by_name()
467                .with(eq(name), eq(TypeId::of::<TestComponent>()))
468                .times(1)
469                .return_const(Ok((
470                    ComponentInstancePtr::new(TestComponent) as ComponentInstanceAnyPtr,
471                    test_cast as CastFunction,
472                )));
473
474            assert!(instance_provider
475                .instance_by_name_typed::<TestComponent>(name)
476                .is_ok());
477        }
478
479        #[test]
480        fn should_provide_instance_by_name_option() {
481            let name = "name";
482
483            let mut instance_provider = MockComponentInstanceProvider::new();
484            instance_provider
485                .expect_instance_by_name()
486                .with(eq(name), eq(TypeId::of::<TestComponent>()))
487                .times(1)
488                .return_const(Ok((
489                    ComponentInstancePtr::new(TestComponent) as ComponentInstanceAnyPtr,
490                    test_cast as CastFunction,
491                )));
492
493            assert!(instance_provider
494                .instance_by_name_option::<TestComponent>(name)
495                .unwrap()
496                .is_some());
497        }
498    }
499}