clasp_federation/
namespace.rs1use std::collections::HashMap;
7
8#[derive(Debug)]
13pub struct NamespaceManager {
14 peer_namespaces: HashMap<String, Vec<String>>,
16 local_namespaces: Vec<String>,
18}
19
20impl NamespaceManager {
21 pub fn new(local_namespaces: Vec<String>) -> Self {
23 Self {
24 peer_namespaces: HashMap::new(),
25 local_namespaces,
26 }
27 }
28
29 pub fn register_peer(&mut self, router_id: &str, patterns: Vec<String>) {
31 self.peer_namespaces.insert(router_id.to_string(), patterns);
32 }
33
34 pub fn remove_peer(&mut self, router_id: &str) {
36 self.peer_namespaces.remove(router_id);
37 }
38
39 pub fn peers_for_address(&self, address: &str, exclude_origin: Option<&str>) -> Vec<String> {
44 self.peer_namespaces
45 .iter()
46 .filter(|(router_id, patterns)| {
47 if let Some(origin) = exclude_origin {
49 if router_id.as_str() == origin {
50 return false;
51 }
52 }
53 patterns
55 .iter()
56 .any(|p| clasp_core::address::glob_match(p, address))
57 })
58 .map(|(id, _)| id.clone())
59 .collect()
60 }
61
62 pub fn is_local(&self, address: &str) -> bool {
64 self.local_namespaces
65 .iter()
66 .any(|p| clasp_core::address::glob_match(p, address))
67 }
68
69 pub fn is_remote(&self, address: &str) -> bool {
71 self.peer_namespaces.values().any(|patterns| {
72 patterns
73 .iter()
74 .any(|p| clasp_core::address::glob_match(p, address))
75 })
76 }
77
78 pub fn find_conflicts(&self) -> Vec<(String, String, String)> {
82 let mut conflicts = Vec::new();
83 let peers: Vec<_> = self.peer_namespaces.iter().collect();
84
85 for i in 0..peers.len() {
86 for j in (i + 1)..peers.len() {
87 let (id_a, patterns_a) = peers[i];
88 let (id_b, patterns_b) = peers[j];
89
90 for pa in patterns_a {
91 for pb in patterns_b {
92 if patterns_overlap(pa, pb) {
93 conflicts.push((
94 format!("{} <-> {}", pa, pb),
95 id_a.clone(),
96 id_b.clone(),
97 ));
98 }
99 }
100 }
101 }
102 }
103
104 conflicts
105 }
106
107 pub fn peer_patterns(&self, router_id: &str) -> Option<&Vec<String>> {
109 self.peer_namespaces.get(router_id)
110 }
111
112 pub fn peers(&self) -> Vec<String> {
114 self.peer_namespaces.keys().cloned().collect()
115 }
116
117 pub fn local_patterns(&self) -> &[String] {
119 &self.local_namespaces
120 }
121
122 pub fn peer_count(&self) -> usize {
124 self.peer_namespaces.len()
125 }
126}
127
128fn patterns_overlap(a: &str, b: &str) -> bool {
133 if a == "/**" || a == "**" || b == "/**" || b == "**" {
135 return true;
136 }
137
138 let parts_a: Vec<&str> = a.split('/').filter(|s| !s.is_empty()).collect();
141 let parts_b: Vec<&str> = b.split('/').filter(|s| !s.is_empty()).collect();
142
143 let min_len = parts_a.len().min(parts_b.len());
144 for i in 0..min_len {
145 let pa = parts_a[i];
146 let pb = parts_b[i];
147
148 if pa == "*" || pa == "**" || pb == "*" || pb == "**" {
150 return true;
151 }
152
153 if pa != pb {
155 return false;
156 }
157 }
158
159 true
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_namespace_basics() {
170 let mut ns = NamespaceManager::new(vec!["/local/**".to_string()]);
171
172 ns.register_peer("peer-a", vec!["/site-a/**".to_string()]);
173 ns.register_peer("peer-b", vec!["/site-b/**".to_string()]);
174
175 assert!(ns.is_local("/local/foo"));
176 assert!(!ns.is_local("/site-a/foo"));
177
178 assert!(ns.is_remote("/site-a/foo"));
179 assert!(ns.is_remote("/site-b/foo"));
180 assert!(!ns.is_remote("/local/foo"));
181 }
182
183 #[test]
184 fn test_peers_for_address() {
185 let mut ns = NamespaceManager::new(vec!["/local/**".to_string()]);
186 ns.register_peer("peer-a", vec!["/shared/**".to_string()]);
187 ns.register_peer("peer-b", vec!["/shared/**".to_string()]);
188
189 let peers = ns.peers_for_address("/shared/foo", None);
190 assert_eq!(peers.len(), 2);
191
192 let peers = ns.peers_for_address("/shared/foo", Some("peer-a"));
194 assert_eq!(peers.len(), 1);
195 assert_eq!(peers[0], "peer-b");
196 }
197
198 #[test]
199 fn test_remove_peer() {
200 let mut ns = NamespaceManager::new(vec![]);
201 ns.register_peer("peer-a", vec!["/a/**".to_string()]);
202 assert_eq!(ns.peer_count(), 1);
203
204 ns.remove_peer("peer-a");
205 assert_eq!(ns.peer_count(), 0);
206 assert!(!ns.is_remote("/a/foo"));
207 }
208
209 #[test]
210 fn test_conflict_detection() {
211 let mut ns = NamespaceManager::new(vec![]);
212 ns.register_peer("peer-a", vec!["/shared/**".to_string()]);
213 ns.register_peer("peer-b", vec!["/shared/**".to_string()]);
214
215 let conflicts = ns.find_conflicts();
216 assert_eq!(conflicts.len(), 1);
217 }
218
219 #[test]
220 fn test_no_conflict_disjoint() {
221 let mut ns = NamespaceManager::new(vec![]);
222 ns.register_peer("peer-a", vec!["/site-a/**".to_string()]);
223 ns.register_peer("peer-b", vec!["/site-b/**".to_string()]);
224
225 let conflicts = ns.find_conflicts();
226 assert!(conflicts.is_empty());
227 }
228
229 #[test]
230 fn test_patterns_overlap() {
231 assert!(patterns_overlap("/**", "/anything"));
232 assert!(patterns_overlap("/a/**", "/a/b/c"));
233 assert!(!patterns_overlap("/a/**", "/b/**"));
234 assert!(patterns_overlap("/shared/**", "/shared/**"));
235 assert!(!patterns_overlap("/site-a/data", "/site-b/data"));
236 }
237}