Skip to main content

minco_core/
service.rs

1use std::{
2    any::{Any, TypeId},
3    collections::HashMap,
4    sync::Arc,
5};
6use thiserror::Error;
7
8#[derive(Default)]
9pub struct ServiceCollection {
10    services: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
11}
12
13impl std::fmt::Debug for ServiceCollection {
14    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        formatter
16            .debug_struct("ServiceCollection")
17            .field("service_count", &self.services.len())
18            .finish()
19    }
20}
21
22impl ServiceCollection {
23    pub fn insert<T>(&mut self, value: Arc<T>) -> Result<(), ServiceError>
24    where
25        T: Any + Send + Sync,
26    {
27        let type_id = TypeId::of::<T>();
28        if self.services.contains_key(&type_id) {
29            return Err(ServiceError::Duplicate(std::any::type_name::<T>()));
30        }
31        self.services.insert(type_id, value);
32        Ok(())
33    }
34
35    pub fn contains<T>(&self) -> bool
36    where
37        T: Any + Send + Sync,
38    {
39        self.services.contains_key(&TypeId::of::<T>())
40    }
41
42    pub fn freeze(self) -> FrozenServices {
43        FrozenServices {
44            services: self.services,
45        }
46    }
47}
48
49#[derive(Clone, Default)]
50pub struct FrozenServices {
51    services: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
52}
53
54impl std::fmt::Debug for FrozenServices {
55    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        formatter
57            .debug_struct("FrozenServices")
58            .field("service_count", &self.services.len())
59            .finish()
60    }
61}
62
63impl FrozenServices {
64    pub fn get<T>(&self) -> Result<Arc<T>, ServiceError>
65    where
66        T: Any + Send + Sync,
67    {
68        self.services
69            .get(&TypeId::of::<T>())
70            .cloned()
71            .ok_or_else(|| ServiceError::Missing(std::any::type_name::<T>()))?
72            .downcast::<T>()
73            .map_err(|_| ServiceError::TypeMismatch(std::any::type_name::<T>()))
74    }
75}
76
77#[derive(Debug, Error, Clone, PartialEq, Eq)]
78pub enum ServiceError {
79    #[error("service is already registered: {0}")]
80    Duplicate(&'static str),
81    #[error("service is not registered: {0}")]
82    Missing(&'static str),
83    #[error("service has an unexpected concrete type: {0}")]
84    TypeMismatch(&'static str),
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn typed_services_are_frozen_and_retrieved_without_string_keys() {
93        let mut services = ServiceCollection::default();
94        services.insert(Arc::new(String::from("hello"))).unwrap();
95        let frozen = services.freeze();
96        assert_eq!(&*frozen.get::<String>().unwrap(), "hello");
97    }
98
99    #[test]
100    fn duplicate_service_types_are_rejected() {
101        let mut services = ServiceCollection::default();
102        services.insert(Arc::new(1_u64)).unwrap();
103        assert!(matches!(
104            services.insert(Arc::new(2_u64)),
105            Err(ServiceError::Duplicate(_))
106        ));
107    }
108}