Skip to main content

rskit_messaging/
registry.rs

1//! Explicit messaging adapter registry.
2
3use std::collections::BTreeMap;
4use std::sync::Arc;
5
6use rskit_errors::{AppError, AppResult, ErrorCode};
7
8use crate::config::BrokerConfig;
9use crate::traits::{MessageConsumer, MessageProducer};
10
11/// Producer/consumer pair created for a messaging backend.
12pub struct MessagingBackend<T: Send + Sync + 'static> {
13    /// Producer for the selected backend.
14    pub producer: Arc<dyn MessageProducer<T>>,
15    /// Consumer for the selected backend.
16    pub consumer: Arc<dyn MessageConsumer<T>>,
17}
18
19/// Factory for a named messaging backend.
20pub trait MessagingFactory<T: Send + Sync + 'static>: Send + Sync {
21    /// Build a producer from broker configuration.
22    fn create_producer(&self, config: &BrokerConfig) -> AppResult<Arc<dyn MessageProducer<T>>>;
23
24    /// Build a consumer from broker configuration.
25    fn create_consumer(&self, config: &BrokerConfig) -> AppResult<Arc<dyn MessageConsumer<T>>>;
26
27    /// Build a producer/consumer pair from broker configuration.
28    fn create(&self, config: &BrokerConfig) -> AppResult<MessagingBackend<T>> {
29        Ok(MessagingBackend {
30            producer: self.create_producer(config)?,
31            consumer: self.create_consumer(config)?,
32        })
33    }
34}
35
36/// Application-owned registry of messaging adapter factories.
37///
38/// Adapters are registered explicitly by application composition code. Importing
39/// an adapter module does not mutate global state or dial external services.
40pub struct MessagingRegistry<T: Send + Sync + 'static> {
41    factories: BTreeMap<String, Arc<dyn MessagingFactory<T>>>,
42}
43
44impl<T: Send + Sync + 'static> MessagingRegistry<T> {
45    /// Create an empty messaging registry.
46    #[must_use]
47    pub fn new() -> Self {
48        Self {
49            factories: BTreeMap::new(),
50        }
51    }
52
53    /// Register a backend factory under `adapter`.
54    pub fn register_backend(
55        &mut self,
56        adapter: impl Into<String>,
57        factory: Arc<dyn MessagingFactory<T>>,
58    ) -> AppResult<()> {
59        let adapter = adapter.into().trim().to_owned();
60        BrokerConfig::new(adapter.clone()).validate()?;
61        if self.factories.contains_key(&adapter) {
62            return Err(AppError::new(
63                ErrorCode::AlreadyExists,
64                format!("messaging adapter '{adapter}' is already registered"),
65            ));
66        }
67        self.factories.insert(adapter, factory);
68        Ok(())
69    }
70
71    /// Build producer and consumer for [`BrokerConfig::adapter`].
72    pub fn build(&self, config: &BrokerConfig) -> AppResult<MessagingBackend<T>> {
73        config.validate()?;
74        let factory = self.factories.get(&config.adapter).ok_or_else(|| {
75            AppError::new(
76                ErrorCode::NotFound,
77                format!("messaging adapter '{}' is not registered", config.adapter),
78            )
79        })?;
80        factory.create(config)
81    }
82
83    /// Construct a producer for [`BrokerConfig::adapter`].
84    pub fn producer(&self, config: &BrokerConfig) -> AppResult<Arc<dyn MessageProducer<T>>> {
85        config.validate()?;
86        let factory = self.factories.get(&config.adapter).ok_or_else(|| {
87            AppError::new(
88                ErrorCode::NotFound,
89                format!("messaging adapter '{}' is not registered", config.adapter),
90            )
91        })?;
92        factory.create_producer(config)
93    }
94
95    /// Construct a consumer for [`BrokerConfig::adapter`].
96    pub fn consumer(&self, config: &BrokerConfig) -> AppResult<Arc<dyn MessageConsumer<T>>> {
97        config.validate()?;
98        let factory = self.factories.get(&config.adapter).ok_or_else(|| {
99            AppError::new(
100                ErrorCode::NotFound,
101                format!("messaging adapter '{}' is not registered", config.adapter),
102            )
103        })?;
104        factory.create_consumer(config)
105    }
106
107    /// Return registered adapter names.
108    #[must_use]
109    pub fn adapters(&self) -> Vec<&str> {
110        self.factories.keys().map(String::as_str).collect()
111    }
112
113    /// Return true when no adapters are registered.
114    #[must_use]
115    pub fn is_empty(&self) -> bool {
116        self.factories.is_empty()
117    }
118
119    /// Number of registered adapters.
120    #[must_use]
121    pub fn len(&self) -> usize {
122        self.factories.len()
123    }
124
125    /// Return true when `adapter` is registered.
126    #[must_use]
127    pub fn contains(&self, adapter: &str) -> bool {
128        self.factories.contains_key(adapter)
129    }
130}
131
132impl<T: Send + Sync + 'static> Default for MessagingRegistry<T> {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use crate::memory::InMemoryBroker;
142    use crate::message::Message;
143    use async_trait::async_trait;
144    use std::sync::atomic::{AtomicUsize, Ordering};
145    use std::time::Duration;
146
147    #[test]
148    fn new_registry_is_empty_and_has_no_side_effects() {
149        let registry = MessagingRegistry::<String>::new();
150        assert!(registry.is_empty());
151        assert!(registry.adapters().is_empty());
152        assert!(registry.producer(&BrokerConfig::default()).is_err());
153    }
154
155    #[test]
156    fn rejects_duplicate_adapter() {
157        let mut registry = MessagingRegistry::<String>::new();
158        let broker = InMemoryBroker::new(8);
159        crate::memory::register(&mut registry, broker.clone()).unwrap();
160
161        let err = crate::memory::register(&mut registry, broker).unwrap_err();
162
163        assert_eq!(err.code(), ErrorCode::AlreadyExists);
164    }
165
166    #[test]
167    fn rejects_blank_adapter_registration() {
168        let mut registry = MessagingRegistry::<String>::new();
169        let factory = CountingFactory {
170            producer_calls: Arc::new(AtomicUsize::new(0)),
171            consumer_calls: Arc::new(AtomicUsize::new(0)),
172        };
173
174        let err = registry
175            .register_backend(" ", Arc::new(factory))
176            .expect_err("blank adapter must fail");
177
178        assert_eq!(err.code(), ErrorCode::InvalidInput);
179    }
180
181    #[test]
182    fn normalizes_adapter_registration_name() {
183        let mut registry = MessagingRegistry::<String>::new();
184        let factory = CountingFactory {
185            producer_calls: Arc::new(AtomicUsize::new(0)),
186            consumer_calls: Arc::new(AtomicUsize::new(0)),
187        };
188
189        registry
190            .register_backend(" counting ", Arc::new(factory))
191            .unwrap();
192
193        assert!(registry.contains("counting"));
194        assert!(!registry.contains(" counting "));
195    }
196
197    #[test]
198    fn producer_lookup_does_not_construct_consumer() {
199        let producer_calls = Arc::new(AtomicUsize::new(0));
200        let consumer_calls = Arc::new(AtomicUsize::new(0));
201        let factory = CountingFactory {
202            producer_calls: Arc::clone(&producer_calls),
203            consumer_calls: Arc::clone(&consumer_calls),
204        };
205        let mut registry = MessagingRegistry::<String>::new();
206        registry
207            .register_backend("counting", Arc::new(factory))
208            .unwrap();
209
210        let _producer = registry.producer(&BrokerConfig::new("counting")).unwrap();
211
212        assert_eq!(producer_calls.load(Ordering::SeqCst), 1);
213        assert_eq!(consumer_calls.load(Ordering::SeqCst), 0);
214    }
215
216    struct CountingFactory {
217        producer_calls: Arc<AtomicUsize>,
218        consumer_calls: Arc<AtomicUsize>,
219    }
220
221    impl MessagingFactory<String> for CountingFactory {
222        fn create_producer(
223            &self,
224            _config: &BrokerConfig,
225        ) -> AppResult<Arc<dyn MessageProducer<String>>> {
226            self.producer_calls.fetch_add(1, Ordering::SeqCst);
227            Ok(Arc::new(NoopProducer))
228        }
229
230        fn create_consumer(
231            &self,
232            _config: &BrokerConfig,
233        ) -> AppResult<Arc<dyn MessageConsumer<String>>> {
234            self.consumer_calls.fetch_add(1, Ordering::SeqCst);
235            Ok(Arc::new(NoopConsumer))
236        }
237    }
238
239    struct NoopProducer;
240
241    #[async_trait]
242    impl MessageProducer<String> for NoopProducer {
243        async fn send(&self, _msg: Message<String>) -> AppResult<()> {
244            Ok(())
245        }
246
247        async fn send_batch(&self, _msgs: Vec<Message<String>>) -> AppResult<()> {
248            Ok(())
249        }
250
251        async fn flush(&self, _timeout: Duration) -> AppResult<()> {
252            Ok(())
253        }
254    }
255
256    struct NoopConsumer;
257
258    #[async_trait]
259    impl MessageConsumer<String> for NoopConsumer {
260        async fn subscribe(&self, _topics: &[&str]) -> AppResult<()> {
261            Ok(())
262        }
263
264        async fn recv(&self) -> AppResult<Message<String>> {
265            Err(AppError::new(ErrorCode::NotFound, "no messages"))
266        }
267    }
268}