springtime_di/component_registry/
conditional.rs

1//! Conditional component definition registration support.
2
3use crate::component::Injectable;
4use crate::component_registry::{ComponentAliasMetadata, ComponentMetadata};
5#[cfg(test)]
6use mockall::automock;
7use std::any::TypeId;
8
9/// A read-only facade of a [ComponentDefinitionRegistry](super::ComponentDefinitionRegistry) safe
10/// to use in registration conditions.
11#[cfg_attr(test, automock)]
12pub trait ComponentDefinitionRegistryFacade {
13    /// Checks if given type is present in this registry.
14    fn is_registered(&self, target: TypeId) -> bool;
15
16    /// Checks if there's a definition with given name.
17    fn is_name_registered(&self, name: &str) -> bool;
18}
19
20/// Context information for use by condition implementations.
21pub trait Context {
22    /// Returns the registry for which the conditional evaluation is taking place.
23    fn registry(&self) -> &dyn ComponentDefinitionRegistryFacade;
24}
25
26/// Factory for contexts for conditional component registration.
27pub trait ContextFactory {
28    /// Creates a new context when starting evaluation.
29    fn create_context<'a>(
30        &self,
31        registry: &'a dyn ComponentDefinitionRegistryFacade,
32    ) -> Box<dyn Context + 'a>;
33}
34
35/// Metadata for the entity which is currently evaluated for registration.
36#[derive(Clone, Debug, Copy)]
37pub enum ConditionMetadata<'a> {
38    Component {
39        type_id: TypeId,
40        metadata: &'a ComponentMetadata,
41    },
42    Alias {
43        alias_type: TypeId,
44        target_type: TypeId,
45        metadata: &'a ComponentAliasMetadata,
46    },
47}
48
49/// Registration condition which should pass to let given [ConditionMetadata] be registered.
50pub type ComponentCondition = fn(context: &dyn Context, metadata: ConditionMetadata) -> bool;
51
52struct SimpleContext<'a> {
53    registry: &'a dyn ComponentDefinitionRegistryFacade,
54}
55
56impl Context for SimpleContext<'_> {
57    fn registry(&self) -> &dyn ComponentDefinitionRegistryFacade {
58        self.registry
59    }
60}
61
62/// Factory producing contexts containing only the necessary data and noting more.
63#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
64pub struct SimpleContextFactory;
65
66impl ContextFactory for SimpleContextFactory {
67    fn create_context<'a>(
68        &self,
69        registry: &'a dyn ComponentDefinitionRegistryFacade,
70    ) -> Box<dyn Context + 'a> {
71        Box::new(SimpleContext { registry })
72    }
73}
74
75/// Simple condition returning true if the given type is already registered.
76pub fn registered_component<T: Injectable + ?Sized>(
77    context: &dyn Context,
78    _metadata: ConditionMetadata,
79) -> bool {
80    context.registry().is_registered(TypeId::of::<T>())
81}
82
83/// Simple condition returning true if the given type is not registered yet.
84pub fn unregistered_component<T: Injectable + ?Sized>(
85    context: &dyn Context,
86    metadata: ConditionMetadata,
87) -> bool {
88    !registered_component::<T>(context, metadata)
89}
90
91/// Returns true if no given name is already registered.
92pub fn unregistered_name(context: &dyn Context, metadata: ConditionMetadata) -> bool {
93    let registry = context.registry();
94    match metadata {
95        ConditionMetadata::Component {
96            metadata: ComponentMetadata { names, .. },
97            ..
98        } => !names.iter().any(|name| registry.is_name_registered(name)),
99        ConditionMetadata::Alias { .. } => true,
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    #[cfg(not(feature = "async"))]
106    mod sync {
107        use crate::component::Injectable;
108        use crate::component_registry::conditional::{
109            registered_component, unregistered_component, unregistered_name, ConditionMetadata,
110            MockComponentDefinitionRegistryFacade, SimpleContext,
111        };
112        use crate::component_registry::{ComponentAliasMetadata, ComponentMetadata};
113        use crate::instance_provider::ComponentInstanceProviderError;
114        use crate::instance_provider::{ComponentInstanceAnyPtr, ComponentInstanceProvider};
115        use mockall::predicate::*;
116        use mockall::Sequence;
117        use std::any::{Any, TypeId};
118
119        struct TestComponent;
120
121        impl Injectable for TestComponent {}
122
123        fn test_constructor(
124            _instance_provider: &mut dyn ComponentInstanceProvider,
125        ) -> Result<ComponentInstanceAnyPtr, ComponentInstanceProviderError> {
126            Err(ComponentInstanceProviderError::NoNamedInstance(
127                "".to_string(),
128            ))
129        }
130
131        fn test_cast(
132            instance: ComponentInstanceAnyPtr,
133        ) -> Result<Box<dyn Any>, ComponentInstanceAnyPtr> {
134            Err(instance)
135        }
136
137        #[test]
138        fn should_check_for_component_existence() {
139            let mut seq = Sequence::new();
140
141            let mut registry = MockComponentDefinitionRegistryFacade::new();
142            registry
143                .expect_is_registered()
144                .with(eq(TypeId::of::<TestComponent>()))
145                .times(2)
146                .in_sequence(&mut seq)
147                .return_const(true);
148            registry
149                .expect_is_registered()
150                .with(eq(TypeId::of::<TestComponent>()))
151                .times(2)
152                .in_sequence(&mut seq)
153                .return_const(false);
154
155            let context = SimpleContext {
156                registry: &registry,
157            };
158            let metadata = ComponentAliasMetadata {
159                is_primary: false,
160                scope: None,
161                cast: test_cast,
162            };
163            let metadata = ConditionMetadata::Alias {
164                alias_type: TypeId::of::<i8>(),
165                target_type: TypeId::of::<TestComponent>(),
166                metadata: &metadata,
167            };
168
169            assert!(registered_component::<TestComponent>(&context, metadata));
170            assert!(!unregistered_component::<TestComponent>(&context, metadata));
171            assert!(!registered_component::<TestComponent>(&context, metadata));
172            assert!(unregistered_component::<TestComponent>(&context, metadata));
173        }
174
175        #[test]
176        fn should_check_for_name_existence() {
177            let mut registry = MockComponentDefinitionRegistryFacade::new();
178            registry
179                .expect_is_name_registered()
180                .with(eq("n1"))
181                .times(1)
182                .return_const(true);
183            registry
184                .expect_is_name_registered()
185                .with(eq("n2"))
186                .times(1)
187                .return_const(false);
188
189            let context = SimpleContext {
190                registry: &registry,
191            };
192
193            let metadata = ComponentMetadata {
194                names: ["n2".to_string(), "n1".to_string()].into_iter().collect(),
195                scope: "".to_string(),
196                constructor: test_constructor,
197                cast: test_cast,
198            };
199            let metadata = ConditionMetadata::Component {
200                type_id: TypeId::of::<TestComponent>(),
201                metadata: &metadata,
202            };
203
204            assert!(!unregistered_name(&context, metadata));
205
206            let metadata = ComponentAliasMetadata {
207                is_primary: false,
208                scope: None,
209                cast: test_cast,
210            };
211            let metadata = ConditionMetadata::Alias {
212                alias_type: TypeId::of::<i8>(),
213                target_type: TypeId::of::<TestComponent>(),
214                metadata: &metadata,
215            };
216
217            assert!(unregistered_name(&context, metadata));
218        }
219    }
220}