Skip to main content

minco_core/
contribution.rs

1use crate::Shared;
2use std::{
3    any::{Any, TypeId},
4    collections::HashMap,
5    sync::Arc,
6};
7
8/// Mutable, typed multi-binding registry used while plugins are installed.
9///
10/// Services represent a single authoritative implementation for a type. Contributions
11/// represent zero or more independent implementations, such as HTTP modules, notification
12/// sinks, health checks, or event observers.
13#[derive(Default)]
14pub struct ContributionCollection {
15    values: HashMap<TypeId, Vec<Arc<dyn Any + Send + Sync>>>,
16}
17
18impl std::fmt::Debug for ContributionCollection {
19    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        formatter
21            .debug_struct("ContributionCollection")
22            .field("contribution_types", &self.values.len())
23            .field(
24                "contribution_count",
25                &self.values.values().map(Vec::len).sum::<usize>(),
26            )
27            .finish()
28    }
29}
30
31impl ContributionCollection {
32    /// Adds one concrete contribution.
33    pub fn push<T>(&mut self, value: Arc<T>)
34    where
35        T: Any + Send + Sync,
36    {
37        self.values
38            .entry(TypeId::of::<T>())
39            .or_default()
40            .push(value);
41    }
42
43    /// Adds one trait-object contribution while retaining a typed retrieval API.
44    pub fn push_shared<T>(&mut self, value: Arc<T>)
45    where
46        T: ?Sized + Send + Sync + 'static,
47    {
48        self.push(Arc::new(Shared::new(value)));
49    }
50
51    /// Returns all concrete contributions currently registered for `T`.
52    pub fn get<T>(&self) -> Vec<Arc<T>>
53    where
54        T: Any + Send + Sync,
55    {
56        self.values
57            .get(&TypeId::of::<T>())
58            .into_iter()
59            .flatten()
60            .filter_map(|value| Arc::clone(value).downcast::<T>().ok())
61            .collect()
62    }
63
64    /// Returns all trait-object contributions currently registered for `T`.
65    pub fn get_shared<T>(&self) -> Vec<Arc<T>>
66    where
67        T: ?Sized + Send + Sync + 'static,
68    {
69        self.get::<Shared<T>>()
70            .into_iter()
71            .map(|value| value.inner())
72            .collect()
73    }
74
75    pub fn freeze(self) -> FrozenContributions {
76        FrozenContributions {
77            values: self.values,
78        }
79    }
80}
81
82/// Immutable contribution registry exposed by a composed application.
83#[derive(Clone, Default)]
84pub struct FrozenContributions {
85    values: HashMap<TypeId, Vec<Arc<dyn Any + Send + Sync>>>,
86}
87
88impl std::fmt::Debug for FrozenContributions {
89    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        formatter
91            .debug_struct("FrozenContributions")
92            .field("contribution_types", &self.values.len())
93            .field(
94                "contribution_count",
95                &self.values.values().map(Vec::len).sum::<usize>(),
96            )
97            .finish()
98    }
99}
100
101impl FrozenContributions {
102    pub fn get<T>(&self) -> Vec<Arc<T>>
103    where
104        T: Any + Send + Sync,
105    {
106        self.values
107            .get(&TypeId::of::<T>())
108            .into_iter()
109            .flatten()
110            .filter_map(|value| Arc::clone(value).downcast::<T>().ok())
111            .collect()
112    }
113
114    pub fn get_shared<T>(&self) -> Vec<Arc<T>>
115    where
116        T: ?Sized + Send + Sync + 'static,
117    {
118        self.get::<Shared<T>>()
119            .into_iter()
120            .map(|value| value.inner())
121            .collect()
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    trait Named: Send + Sync {
130        fn name(&self) -> &'static str;
131    }
132
133    #[derive(Debug)]
134    struct First;
135    impl Named for First {
136        fn name(&self) -> &'static str {
137            "first"
138        }
139    }
140
141    #[derive(Debug)]
142    struct Second;
143    impl Named for Second {
144        fn name(&self) -> &'static str {
145            "second"
146        }
147    }
148
149    #[test]
150    fn shared_contributions_preserve_registration_order() {
151        let mut values = ContributionCollection::default();
152        values.push_shared::<dyn Named>(Arc::new(First));
153        values.push_shared::<dyn Named>(Arc::new(Second));
154
155        let names = values
156            .freeze()
157            .get_shared::<dyn Named>()
158            .into_iter()
159            .map(|value| value.name())
160            .collect::<Vec<_>>();
161
162        assert_eq!(names, ["first", "second"]);
163    }
164}