elif_core/container/
descriptor.rs

1use crate::container::scope::ServiceScope;
2use crate::errors::CoreError;
3use std::any::{Any, TypeId};
4
5/// Service identifier combining type and optional name
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
7pub struct ServiceId {
8    pub type_id: TypeId,
9    pub type_name: &'static str,
10    pub name: Option<String>,
11}
12
13impl ServiceId {
14    /// Create a new service ID for a type
15    pub fn of<T: 'static + ?Sized>() -> Self {
16        Self {
17            type_id: TypeId::of::<T>(),
18            type_name: std::any::type_name::<T>(),
19            name: None,
20        }
21    }
22
23    /// Create a named service ID for a type
24    pub fn named<T: 'static + ?Sized>(name: impl Into<String>) -> Self {
25        Self {
26            type_id: TypeId::of::<T>(),
27            type_name: std::any::type_name::<T>(),
28            name: Some(name.into()),
29        }
30    }
31
32    /// Check if this ServiceId matches a type and name without allocating
33    pub fn matches_named<T: 'static + ?Sized>(&self, name: &str) -> bool {
34        self.type_id == TypeId::of::<T>() && self.name.as_deref() == Some(name)
35    }
36
37    /// Get the type name as a string
38    pub fn type_name(&self) -> &'static str {
39        self.type_name
40    }
41
42    /// Create a service ID directly from type IDs and names
43    pub fn by_ids(type_id: TypeId, type_name: &'static str) -> Self {
44        Self {
45            type_id,
46            type_name,
47            name: None,
48        }
49    }
50
51    /// Create a named service ID directly from type IDs and names
52    pub fn named_by_ids(type_id: TypeId, type_name: &'static str, name: String) -> Self {
53        Self {
54            type_id,
55            type_name,
56            name: Some(name),
57        }
58    }
59}
60
61/// Factory function for creating service instances
62/// We use Any here to avoid circular references with Container
63pub type ServiceFactory =
64    Box<dyn Fn() -> Result<Box<dyn Any + Send + Sync>, CoreError> + Send + Sync>;
65
66/// Strategy for activating/creating service instances
67pub enum ServiceActivationStrategy {
68    /// Service created via factory function (traditional approach)
69    Factory(ServiceFactory),
70    /// Service created via auto-wiring (Injectable trait)
71    AutoWired,
72}
73
74impl std::fmt::Debug for ServiceActivationStrategy {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        match self {
77            ServiceActivationStrategy::Factory(_) => write!(f, "Factory(<factory_fn>)"),
78            ServiceActivationStrategy::AutoWired => write!(f, "AutoWired"),
79        }
80    }
81}
82
83/// Service descriptor containing all metadata for a service
84pub struct ServiceDescriptor {
85    /// Service identifier (type + optional name)
86    pub service_id: ServiceId,
87    /// Implementation type ID
88    pub implementation_id: TypeId,
89    /// Service lifetime/scope
90    pub lifetime: ServiceScope,
91    /// Strategy for creating instances
92    pub activation_strategy: ServiceActivationStrategy,
93    /// Dependencies this service requires
94    pub dependencies: Vec<ServiceId>,
95}
96
97impl std::fmt::Debug for ServiceDescriptor {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        f.debug_struct("ServiceDescriptor")
100            .field("service_id", &self.service_id)
101            .field("implementation_id", &self.implementation_id)
102            .field("lifetime", &self.lifetime)
103            .field("activation_strategy", &self.activation_strategy)
104            .field("dependencies", &self.dependencies)
105            .finish()
106    }
107}
108
109impl ServiceDescriptor {
110    /// Create a new service descriptor with type binding
111    pub fn bind<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
112    ) -> ServiceDescriptorBuilder<TInterface, TImpl> {
113        ServiceDescriptorBuilder::new()
114    }
115
116    /// Create a named service descriptor
117    pub fn bind_named<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
118        name: impl Into<String>,
119    ) -> ServiceDescriptorBuilder<TInterface, TImpl> {
120        ServiceDescriptorBuilder::new().with_name(name)
121    }
122
123    /// Create a singleton service descriptor
124    pub fn singleton<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
125    ) -> ServiceDescriptorBuilder<TInterface, TImpl> {
126        ServiceDescriptorBuilder::new().with_lifetime(ServiceScope::Singleton)
127    }
128
129    /// Create a transient service descriptor
130    pub fn transient<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
131    ) -> ServiceDescriptorBuilder<TInterface, TImpl> {
132        ServiceDescriptorBuilder::new().with_lifetime(ServiceScope::Transient)
133    }
134
135    /// Create an auto-wired service descriptor
136    pub fn autowired<T: 'static>(dependencies: Vec<ServiceId>) -> ServiceDescriptor {
137        ServiceDescriptor {
138            service_id: ServiceId::of::<T>(),
139            implementation_id: TypeId::of::<T>(),
140            lifetime: ServiceScope::Transient,
141            activation_strategy: ServiceActivationStrategy::AutoWired,
142            dependencies,
143        }
144    }
145
146    /// Create an auto-wired singleton service descriptor
147    pub fn autowired_singleton<T: 'static>(dependencies: Vec<ServiceId>) -> ServiceDescriptor {
148        ServiceDescriptor {
149            service_id: ServiceId::of::<T>(),
150            implementation_id: TypeId::of::<T>(),
151            lifetime: ServiceScope::Singleton,
152            activation_strategy: ServiceActivationStrategy::AutoWired,
153            dependencies,
154        }
155    }
156}
157
158/// Builder for service descriptors
159pub struct ServiceDescriptorBuilder<TInterface: ?Sized, TImpl> {
160    name: Option<String>,
161    lifetime: ServiceScope,
162    dependencies: Vec<ServiceId>,
163    _phantom: std::marker::PhantomData<(*const TInterface, TImpl)>,
164}
165
166impl<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static> Default
167    for ServiceDescriptorBuilder<TInterface, TImpl>
168{
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174impl<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>
175    ServiceDescriptorBuilder<TInterface, TImpl>
176{
177    /// Create a new service descriptor builder
178    pub fn new() -> Self {
179        Self {
180            name: None,
181            lifetime: ServiceScope::Transient,
182            dependencies: Vec::new(),
183            _phantom: std::marker::PhantomData,
184        }
185    }
186
187    /// Set the service name
188    pub fn with_name(mut self, name: impl Into<String>) -> Self {
189        self.name = Some(name.into());
190        self
191    }
192
193    /// Set the service lifetime
194    pub fn with_lifetime(mut self, lifetime: ServiceScope) -> Self {
195        self.lifetime = lifetime;
196        self
197    }
198
199    /// Add a dependency
200    pub fn depends_on<T: 'static>(mut self) -> Self {
201        self.dependencies.push(ServiceId::of::<T>());
202        self
203    }
204
205    /// Add a named dependency
206    pub fn depends_on_named<T: 'static>(mut self, name: impl Into<String>) -> Self {
207        self.dependencies.push(ServiceId::named::<T>(name));
208        self
209    }
210
211    /// Build the service descriptor
212    pub fn build(self) -> ServiceDescriptor {
213        let service_id = if let Some(name) = self.name {
214            ServiceId::named::<TInterface>(name)
215        } else {
216            ServiceId::of::<TInterface>()
217        };
218
219        let factory: ServiceFactory = Box::new(move || {
220            let instance = TImpl::default();
221            Ok(Box::new(instance) as Box<dyn Any + Send + Sync>)
222        });
223
224        ServiceDescriptor {
225            service_id,
226            implementation_id: TypeId::of::<TImpl>(),
227            lifetime: self.lifetime,
228            activation_strategy: ServiceActivationStrategy::Factory(factory),
229            dependencies: self.dependencies,
230        }
231    }
232}
233
234/// Service descriptor builder with custom factory
235pub struct ServiceDescriptorFactoryBuilder<TInterface: ?Sized> {
236    name: Option<String>,
237    lifetime: ServiceScope,
238    dependencies: Vec<ServiceId>,
239    factory: Option<ServiceFactory>,
240    _phantom: std::marker::PhantomData<*const TInterface>,
241}
242
243impl<TInterface: ?Sized + 'static> Default for ServiceDescriptorFactoryBuilder<TInterface> {
244    fn default() -> Self {
245        Self::new()
246    }
247}
248
249impl<TInterface: ?Sized + 'static> ServiceDescriptorFactoryBuilder<TInterface> {
250    /// Create a new factory-based service descriptor builder
251    pub fn new() -> Self {
252        Self {
253            name: None,
254            lifetime: ServiceScope::Transient,
255            dependencies: Vec::new(),
256            factory: None,
257            _phantom: std::marker::PhantomData,
258        }
259    }
260
261    /// Set the service name
262    pub fn with_name(mut self, name: impl Into<String>) -> Self {
263        self.name = Some(name.into());
264        self
265    }
266
267    /// Set the service lifetime
268    pub fn with_lifetime(mut self, lifetime: ServiceScope) -> Self {
269        self.lifetime = lifetime;
270        self
271    }
272
273    /// Set the factory function
274    pub fn with_factory<F, T>(mut self, factory: F) -> Self
275    where
276        F: Fn() -> Result<T, CoreError> + Send + Sync + 'static,
277        T: Send + Sync + 'static,
278    {
279        let wrapped_factory: ServiceFactory = Box::new(move || {
280            let instance = factory()?;
281            Ok(Box::new(instance) as Box<dyn Any + Send + Sync>)
282        });
283        self.factory = Some(wrapped_factory);
284        self
285    }
286
287    /// Build the service descriptor
288    pub fn build(self) -> Result<ServiceDescriptor, CoreError> {
289        let factory = self
290            .factory
291            .ok_or_else(|| CoreError::InvalidServiceDescriptor {
292                message: "Factory function is required".to_string(),
293            })?;
294
295        let service_id = if let Some(name) = self.name {
296            ServiceId::named::<TInterface>(name)
297        } else {
298            ServiceId::of::<TInterface>()
299        };
300
301        Ok(ServiceDescriptor {
302            service_id,
303            implementation_id: TypeId::of::<()>(), // Unknown for factory-based services
304            lifetime: self.lifetime,
305            activation_strategy: ServiceActivationStrategy::Factory(factory),
306            dependencies: self.dependencies,
307        })
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use super::*;
314
315    #[allow(dead_code)]
316    trait TestTrait: Send + Sync {
317        fn test_method(&self) -> String;
318    }
319
320    #[derive(Debug, Default)]
321    struct TestImpl;
322
323    unsafe impl Send for TestImpl {}
324    unsafe impl Sync for TestImpl {}
325
326    impl TestTrait for TestImpl {
327        fn test_method(&self) -> String {
328            "test".to_string()
329        }
330    }
331
332    #[test]
333    fn test_service_id_creation() {
334        let id1 = ServiceId::of::<TestImpl>();
335        let id2 = ServiceId::named::<TestImpl>("test");
336
337        assert_eq!(id1.type_id, TypeId::of::<TestImpl>());
338        assert_eq!(id1.name, None);
339
340        assert_eq!(id2.type_id, TypeId::of::<TestImpl>());
341        assert_eq!(id2.name, Some("test".to_string()));
342
343        assert_ne!(id1, id2);
344    }
345
346    #[test]
347    fn test_type_name_capture() {
348        let id1 = ServiceId::of::<TestImpl>();
349        let id2 = ServiceId::named::<TestImpl>("test");
350        let id3 = ServiceId::of::<dyn TestTrait>();
351        let id4 = ServiceId::of::<String>();
352
353        // Verify that type names are actually captured, not "unknown"
354        assert!(id1.type_name().contains("TestImpl"));
355        assert!(id2.type_name().contains("TestImpl"));
356        assert!(id3.type_name().contains("TestTrait"));
357        assert_eq!(id4.type_name(), "alloc::string::String");
358
359        // Verify type_name() method returns the stored value
360        assert_eq!(id1.type_name(), id1.type_name);
361        assert_eq!(id2.type_name(), id2.type_name);
362    }
363
364    #[test]
365    fn test_service_descriptor_builder() {
366        let descriptor = ServiceDescriptor::bind::<dyn TestTrait, TestImpl>()
367            .with_lifetime(ServiceScope::Singleton)
368            .depends_on::<String>()
369            .build();
370
371        assert_eq!(descriptor.lifetime, ServiceScope::Singleton);
372        assert_eq!(descriptor.implementation_id, TypeId::of::<TestImpl>());
373        assert_eq!(descriptor.dependencies.len(), 1);
374        assert_eq!(descriptor.dependencies[0], ServiceId::of::<String>());
375    }
376
377    #[test]
378    fn test_factory_service_descriptor() {
379        let descriptor = ServiceDescriptorFactoryBuilder::<dyn TestTrait>::new()
380            .with_lifetime(ServiceScope::Transient)
381            .with_factory(|| -> Result<TestImpl, CoreError> { Ok(TestImpl) })
382            .build()
383            .unwrap();
384
385        assert_eq!(descriptor.lifetime, ServiceScope::Transient);
386    }
387}