Skip to main content

minco_core/
service.rs

1use std::{
2    any::{Any, TypeId},
3    collections::HashMap,
4    fmt,
5    sync::Arc,
6};
7use thiserror::Error;
8
9use crate::{RegistrationOwner, ServiceRegistration};
10
11/// Sized wrapper that lets the service and contribution registries store an `Arc<dyn Trait>`
12/// without introducing a global service locator or stringly typed key.
13#[derive(Clone)]
14pub struct Shared<T: ?Sized + Send + Sync + 'static> {
15    inner: Arc<T>,
16}
17
18impl<T: ?Sized + Send + Sync + 'static> Shared<T> {
19    pub const fn new(inner: Arc<T>) -> Self {
20        Self { inner }
21    }
22
23    pub fn inner(&self) -> Arc<T> {
24        Arc::clone(&self.inner)
25    }
26}
27
28impl<T: ?Sized + Send + Sync + 'static> std::fmt::Debug for Shared<T> {
29    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        formatter
31            .debug_struct("Shared")
32            .field("service", &std::any::type_name::<T>())
33            .finish_non_exhaustive()
34    }
35}
36
37#[derive(Default)]
38pub struct ServiceCollection {
39    services: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
40    registrations: HashMap<TypeId, (ServiceRegistration, usize)>,
41    next_installation_index: usize,
42}
43
44impl std::fmt::Debug for ServiceCollection {
45    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        formatter
47            .debug_struct("ServiceCollection")
48            .field("service_count", &self.services.len())
49            .field("registration_count", &self.registrations.len())
50            .field("next_installation_index", &self.next_installation_index)
51            .finish()
52    }
53}
54
55impl ServiceCollection {
56    pub fn insert<T>(&mut self, value: Arc<T>) -> Result<(), ServiceError>
57    where
58        T: Any + Send + Sync,
59    {
60        self.insert_owned(value, RegistrationOwner::application())
61    }
62
63    pub(crate) fn insert_owned<T>(
64        &mut self,
65        value: Arc<T>,
66        attempted_owner: RegistrationOwner,
67    ) -> Result<(), ServiceError>
68    where
69        T: Any + Send + Sync,
70    {
71        let type_id = TypeId::of::<T>();
72        let rust_type = std::any::type_name::<T>();
73        if let Some((first, _)) = self.registrations.get(&type_id) {
74            return Err(ServiceError::Duplicate(DuplicateServiceRegistration {
75                rust_type,
76                first_owner: first.owner.clone(),
77                attempted_owner,
78            }));
79        }
80        self.services.insert(type_id, value);
81        self.registrations.insert(
82            type_id,
83            (
84                ServiceRegistration {
85                    rust_type,
86                    owner: attempted_owner,
87                },
88                self.next_installation_index,
89            ),
90        );
91        self.next_installation_index += 1;
92        Ok(())
93    }
94
95    pub fn insert_shared<T>(&mut self, value: Arc<T>) -> Result<(), ServiceError>
96    where
97        T: ?Sized + Send + Sync + 'static,
98    {
99        self.insert_shared_owned(value, RegistrationOwner::application())
100    }
101
102    pub(crate) fn insert_shared_owned<T>(
103        &mut self,
104        value: Arc<T>,
105        owner: RegistrationOwner,
106    ) -> Result<(), ServiceError>
107    where
108        T: ?Sized + Send + Sync + 'static,
109    {
110        self.insert_owned(Arc::new(Shared::new(value)), owner)
111    }
112
113    pub fn contains<T>(&self) -> bool
114    where
115        T: Any + Send + Sync,
116    {
117        self.services.contains_key(&TypeId::of::<T>())
118    }
119
120    pub fn contains_shared<T>(&self) -> bool
121    where
122        T: ?Sized + Send + Sync + 'static,
123    {
124        self.contains::<Shared<T>>()
125    }
126
127    pub fn get<T>(&self) -> Result<Arc<T>, ServiceError>
128    where
129        T: Any + Send + Sync,
130    {
131        get_service(&self.services)
132    }
133
134    pub fn get_optional<T>(&self) -> Result<Option<Arc<T>>, ServiceError>
135    where
136        T: Any + Send + Sync,
137    {
138        get_optional_service(&self.services)
139    }
140
141    pub fn get_shared<T>(&self) -> Result<Arc<T>, ServiceError>
142    where
143        T: ?Sized + Send + Sync + 'static,
144    {
145        self.get::<Shared<T>>().map(|service| service.inner())
146    }
147
148    pub fn get_optional_shared<T>(&self) -> Result<Option<Arc<T>>, ServiceError>
149    where
150        T: ?Sized + Send + Sync + 'static,
151    {
152        self.get_optional::<Shared<T>>()
153            .map(|service| service.map(|value| value.inner()))
154    }
155
156    pub fn freeze(self) -> FrozenServices {
157        let mut registrations = self.registrations.into_values().collect::<Vec<_>>();
158        registrations.sort_by(|(left, left_index), (right, right_index)| {
159            left.rust_type
160                .cmp(right.rust_type)
161                .then_with(|| left_index.cmp(right_index))
162        });
163        let registrations = registrations
164            .into_iter()
165            .map(|(registration, _)| registration)
166            .collect();
167        FrozenServices {
168            services: self.services,
169            registrations,
170        }
171    }
172}
173
174/// Owner-bound singleton registrar exposed by `PluginContext`.
175///
176/// The owner is created by `PluginManager`; plugins can register and retrieve typed values but
177/// cannot select or spoof a registration owner.
178pub struct ServiceRegistrar<'a> {
179    pub(crate) services: &'a mut ServiceCollection,
180    pub(crate) owner: RegistrationOwner,
181}
182
183impl fmt::Debug for ServiceRegistrar<'_> {
184    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
185        formatter
186            .debug_struct("ServiceRegistrar")
187            .field("owner", &self.owner)
188            .finish_non_exhaustive()
189    }
190}
191
192impl ServiceRegistrar<'_> {
193    pub fn insert<T>(&mut self, value: Arc<T>) -> Result<(), ServiceError>
194    where
195        T: Any + Send + Sync,
196    {
197        self.services.insert_owned(value, self.owner.clone())
198    }
199
200    pub fn insert_shared<T>(&mut self, value: Arc<T>) -> Result<(), ServiceError>
201    where
202        T: ?Sized + Send + Sync + 'static,
203    {
204        self.services.insert_shared_owned(value, self.owner.clone())
205    }
206
207    pub fn contains<T>(&self) -> bool
208    where
209        T: Any + Send + Sync,
210    {
211        self.services.contains::<T>()
212    }
213
214    pub fn contains_shared<T>(&self) -> bool
215    where
216        T: ?Sized + Send + Sync + 'static,
217    {
218        self.services.contains_shared::<T>()
219    }
220
221    pub fn get<T>(&self) -> Result<Arc<T>, ServiceError>
222    where
223        T: Any + Send + Sync,
224    {
225        self.services.get::<T>()
226    }
227
228    pub fn get_optional<T>(&self) -> Result<Option<Arc<T>>, ServiceError>
229    where
230        T: Any + Send + Sync,
231    {
232        self.services.get_optional::<T>()
233    }
234
235    pub fn get_shared<T>(&self) -> Result<Arc<T>, ServiceError>
236    where
237        T: ?Sized + Send + Sync + 'static,
238    {
239        self.services.get_shared::<T>()
240    }
241
242    pub fn get_optional_shared<T>(&self) -> Result<Option<Arc<T>>, ServiceError>
243    where
244        T: ?Sized + Send + Sync + 'static,
245    {
246        self.services.get_optional_shared::<T>()
247    }
248}
249
250#[derive(Clone, Default)]
251pub struct FrozenServices {
252    services: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
253    registrations: Vec<ServiceRegistration>,
254}
255
256impl std::fmt::Debug for FrozenServices {
257    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258        formatter
259            .debug_struct("FrozenServices")
260            .field("service_count", &self.services.len())
261            .field("registration_count", &self.registrations.len())
262            .finish()
263    }
264}
265
266impl FrozenServices {
267    /// Returns deterministic metadata without exposing service values.
268    pub fn registrations(&self) -> &[ServiceRegistration] {
269        &self.registrations
270    }
271
272    pub fn get<T>(&self) -> Result<Arc<T>, ServiceError>
273    where
274        T: Any + Send + Sync,
275    {
276        get_service(&self.services)
277    }
278
279    pub fn get_optional<T>(&self) -> Result<Option<Arc<T>>, ServiceError>
280    where
281        T: Any + Send + Sync,
282    {
283        get_optional_service(&self.services)
284    }
285
286    pub fn get_shared<T>(&self) -> Result<Arc<T>, ServiceError>
287    where
288        T: ?Sized + Send + Sync + 'static,
289    {
290        self.get::<Shared<T>>().map(|service| service.inner())
291    }
292
293    pub fn get_optional_shared<T>(&self) -> Result<Option<Arc<T>>, ServiceError>
294    where
295        T: ?Sized + Send + Sync + 'static,
296    {
297        self.get_optional::<Shared<T>>()
298            .map(|service| service.map(|value| value.inner()))
299    }
300}
301
302fn get_service<T>(
303    services: &HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
304) -> Result<Arc<T>, ServiceError>
305where
306    T: Any + Send + Sync,
307{
308    services
309        .get(&TypeId::of::<T>())
310        .cloned()
311        .ok_or_else(|| ServiceError::Missing(std::any::type_name::<T>()))?
312        .downcast::<T>()
313        .map_err(|_| ServiceError::TypeMismatch(std::any::type_name::<T>()))
314}
315
316fn get_optional_service<T>(
317    services: &HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
318) -> Result<Option<Arc<T>>, ServiceError>
319where
320    T: Any + Send + Sync,
321{
322    services
323        .get(&TypeId::of::<T>())
324        .cloned()
325        .map(|value| {
326            value
327                .downcast::<T>()
328                .map_err(|_| ServiceError::TypeMismatch(std::any::type_name::<T>()))
329        })
330        .transpose()
331}
332
333#[derive(Debug, Clone, PartialEq, Eq)]
334pub struct DuplicateServiceRegistration {
335    pub rust_type: &'static str,
336    pub first_owner: RegistrationOwner,
337    pub attempted_owner: RegistrationOwner,
338}
339
340impl fmt::Display for DuplicateServiceRegistration {
341    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
342        write!(
343            formatter,
344            "{} (first owner: {}, attempted owner: {})",
345            self.rust_type, self.first_owner, self.attempted_owner
346        )
347    }
348}
349
350#[derive(Debug, Error, Clone, PartialEq, Eq)]
351pub enum ServiceError {
352    #[error("service is already registered: {0}")]
353    Duplicate(DuplicateServiceRegistration),
354    #[error("service is not registered: {0}")]
355    Missing(&'static str),
356    #[error("service has an unexpected concrete type: {0}")]
357    TypeMismatch(&'static str),
358}
359
360#[cfg(test)]
361mod tests {
362    use super::*;
363
364    trait Greeting: Send + Sync {
365        fn text(&self) -> &'static str;
366    }
367
368    #[derive(Debug)]
369    struct Hello;
370    impl Greeting for Hello {
371        fn text(&self) -> &'static str {
372            "hello"
373        }
374    }
375
376    #[test]
377    fn typed_services_are_frozen_and_retrieved_without_string_keys() {
378        let mut services = ServiceCollection::default();
379        services.insert(Arc::new(String::from("hello"))).unwrap();
380        let frozen = services.freeze();
381        assert_eq!(&*frozen.get::<String>().unwrap(), "hello");
382    }
383
384    #[test]
385    fn trait_object_services_are_registered_without_double_arc_call_sites() {
386        let mut services = ServiceCollection::default();
387        services
388            .insert_shared::<dyn Greeting>(Arc::new(Hello))
389            .unwrap();
390        assert_eq!(
391            services.get_shared::<dyn Greeting>().unwrap().text(),
392            "hello"
393        );
394    }
395
396    #[test]
397    fn optional_service_lookup_distinguishes_absence_from_type_errors() {
398        let services = ServiceCollection::default();
399        assert!(services.get_optional::<String>().unwrap().is_none());
400        assert!(
401            services
402                .get_optional_shared::<dyn Greeting>()
403                .unwrap()
404                .is_none()
405        );
406
407        let frozen = services.freeze();
408        assert!(frozen.get_optional::<String>().unwrap().is_none());
409        assert!(
410            frozen
411                .get_optional_shared::<dyn Greeting>()
412                .unwrap()
413                .is_none()
414        );
415    }
416
417    #[test]
418    fn duplicate_service_types_are_rejected() {
419        let mut services = ServiceCollection::default();
420        services.insert(Arc::new(1_u64)).unwrap();
421        let Err(ServiceError::Duplicate(duplicate)) = services.insert(Arc::new(2_u64)) else {
422            panic!("duplicate must fail");
423        };
424        assert_eq!(duplicate.rust_type, std::any::type_name::<u64>());
425        assert!(duplicate.first_owner.is_application());
426        assert!(duplicate.attempted_owner.is_application());
427        assert_eq!(
428            duplicate.to_string(),
429            "u64 (first owner: application, attempted owner: application)"
430        );
431    }
432}