Skip to main content

mfm_machine/
live_io_registry.rs

1/// Registry primitives for Live IO transport factories.
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use crate::live_io::LiveIoTransportFactory;
6
7/// Error returned when a transport registry operation cannot be completed.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct RegistryError {
10    /// Human-readable explanation of the registration failure.
11    pub message: String,
12}
13
14impl RegistryError {
15    /// Creates a new registry error with the provided message.
16    pub fn new(message: impl Into<String>) -> Self {
17        Self {
18            message: message.into(),
19        }
20    }
21}
22
23impl std::fmt::Display for RegistryError {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{}", self.message)
26    }
27}
28
29impl std::error::Error for RegistryError {}
30
31/// Registry abstraction used to store and resolve Live IO transport factories by namespace group.
32pub trait TransportRegistry: Send + Sync {
33    /// Registers a factory under its namespace group.
34    ///
35    /// Implementations should reject empty groups and duplicate registrations.
36    fn register(&mut self, factory: Arc<dyn LiveIoTransportFactory>) -> Result<(), RegistryError>;
37
38    /// Resolves the factory registered for an exact namespace group.
39    fn resolve(&self, namespace_group: &str) -> Option<Arc<dyn LiveIoTransportFactory>>;
40
41    /// Returns all registered factories in implementation-defined order.
42    fn all(&self) -> Vec<Arc<dyn LiveIoTransportFactory>>;
43}
44
45/// Hash map-backed [`TransportRegistry`] implementation for runtime wiring and tests.
46#[derive(Clone, Default)]
47pub struct HashMapTransportRegistry {
48    routes: HashMap<String, Arc<dyn LiveIoTransportFactory>>,
49}
50
51impl HashMapTransportRegistry {
52    /// Creates an empty registry.
53    pub fn new() -> Self {
54        Self::default()
55    }
56}
57
58impl TransportRegistry for HashMapTransportRegistry {
59    fn register(&mut self, factory: Arc<dyn LiveIoTransportFactory>) -> Result<(), RegistryError> {
60        let group = factory.namespace_group().trim();
61        if group.is_empty() {
62            return Err(RegistryError::new(
63                "transport namespace group must not be empty",
64            ));
65        }
66        if self.routes.contains_key(group) {
67            return Err(RegistryError::new(format!(
68                "duplicate transport namespace group: {group}",
69            )));
70        }
71        self.routes.insert(group.to_string(), factory);
72        Ok(())
73    }
74
75    fn resolve(&self, namespace_group: &str) -> Option<Arc<dyn LiveIoTransportFactory>> {
76        self.routes.get(namespace_group).cloned()
77    }
78
79    fn all(&self) -> Vec<Arc<dyn LiveIoTransportFactory>> {
80        self.routes.values().cloned().collect()
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    use crate::live_io::{LiveIoEnv, LiveIoTransport};
89
90    struct DummyFactory {
91        group: &'static str,
92    }
93
94    impl LiveIoTransportFactory for DummyFactory {
95        fn namespace_group(&self) -> &str {
96            self.group
97        }
98
99        fn make(&self, _env: LiveIoEnv) -> Box<dyn LiveIoTransport> {
100            panic!("not used")
101        }
102    }
103
104    #[test]
105    fn duplicate_namespace_group_is_rejected() {
106        let mut reg = HashMapTransportRegistry::new();
107        reg.register(Arc::new(DummyFactory { group: "local.fs" }))
108            .expect("first registration");
109        let err = reg
110            .register(Arc::new(DummyFactory { group: "local.fs" }))
111            .expect_err("expected duplicate error");
112        assert!(err.message.contains("duplicate"));
113    }
114}