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}