1use std::collections::HashMap;
27use std::time::Instant;
28
29use crate::runtime::KernelId;
30
31#[derive(Debug, Clone)]
33pub struct RegistryEntry {
34 pub name: String,
36 pub kernel_id: KernelId,
38 pub registered_at: Instant,
40 pub tags: HashMap<String, String>,
42}
43
44#[derive(Debug, Clone)]
46pub enum RegistryEvent {
47 Registered {
49 name: String,
51 kernel_id: KernelId,
53 },
54 Deregistered {
56 name: String,
58 kernel_id: KernelId,
60 },
61 Updated {
63 name: String,
65 old_kernel_id: KernelId,
67 new_kernel_id: KernelId,
69 },
70}
71
72pub struct ActorRegistry {
78 entries: HashMap<String, RegistryEntry>,
80 reverse: HashMap<KernelId, Vec<String>>,
82 watchers: Vec<(String, Vec<RegistryEvent>)>,
84}
85
86impl ActorRegistry {
87 pub fn new() -> Self {
89 Self {
90 entries: HashMap::new(),
91 reverse: HashMap::new(),
92 watchers: Vec::new(),
93 }
94 }
95
96 pub fn register(&mut self, name: impl Into<String>, kernel_id: KernelId) -> RegistryEvent {
101 let name = name.into();
102
103 let event = if let Some(existing) = self.entries.get(&name) {
104 let old_id = existing.kernel_id.clone();
105 if let Some(names) = self.reverse.get_mut(&old_id) {
107 names.retain(|n| n != &name);
108 }
109 RegistryEvent::Updated {
110 name: name.clone(),
111 old_kernel_id: old_id,
112 new_kernel_id: kernel_id.clone(),
113 }
114 } else {
115 RegistryEvent::Registered {
116 name: name.clone(),
117 kernel_id: kernel_id.clone(),
118 }
119 };
120
121 self.reverse
123 .entry(kernel_id.clone())
124 .or_default()
125 .push(name.clone());
126
127 self.entries.insert(
128 name.clone(),
129 RegistryEntry {
130 name,
131 kernel_id,
132 registered_at: Instant::now(),
133 tags: HashMap::new(),
134 },
135 );
136
137 self.notify_watchers(&event);
138 event
139 }
140
141 pub fn register_with_tags(
143 &mut self,
144 name: impl Into<String>,
145 kernel_id: KernelId,
146 tags: HashMap<String, String>,
147 ) -> RegistryEvent {
148 let event = self.register(name, kernel_id);
149 if let RegistryEvent::Registered { ref name, .. }
150 | RegistryEvent::Updated { ref name, .. } = event
151 {
152 if let Some(entry) = self.entries.get_mut(name) {
153 entry.tags = tags;
154 }
155 }
156 event
157 }
158
159 pub fn deregister(&mut self, name: &str) -> Option<RegistryEvent> {
161 if let Some(entry) = self.entries.remove(name) {
162 if let Some(names) = self.reverse.get_mut(&entry.kernel_id) {
164 names.retain(|n| n != name);
165 if names.is_empty() {
166 self.reverse.remove(&entry.kernel_id);
167 }
168 }
169
170 let event = RegistryEvent::Deregistered {
171 name: name.to_string(),
172 kernel_id: entry.kernel_id,
173 };
174 self.notify_watchers(&event);
175 Some(event)
176 } else {
177 None
178 }
179 }
180
181 pub fn deregister_kernel(&mut self, kernel_id: &KernelId) -> Vec<RegistryEvent> {
183 let mut events = Vec::new();
184 if let Some(names) = self.reverse.remove(kernel_id) {
185 for name in names {
186 if let Some(entry) = self.entries.remove(&name) {
187 events.push(RegistryEvent::Deregistered {
188 name,
189 kernel_id: entry.kernel_id,
190 });
191 }
192 }
193 }
194 for event in &events {
195 self.notify_watchers(event);
196 }
197 events
198 }
199
200 pub fn lookup(&self, name: &str) -> Option<&KernelId> {
202 self.entries.get(name).map(|e| &e.kernel_id)
203 }
204
205 pub fn lookup_entry(&self, name: &str) -> Option<&RegistryEntry> {
207 self.entries.get(name)
208 }
209
210 pub fn names_for(&self, kernel_id: &KernelId) -> Vec<&str> {
212 self.reverse
213 .get(kernel_id)
214 .map(|names| names.iter().map(String::as_str).collect())
215 .unwrap_or_default()
216 }
217
218 pub fn lookup_pattern(&self, pattern: &str) -> Vec<(&str, &KernelId)> {
230 self.entries
231 .iter()
232 .filter(|(name, _)| wildcard_match(pattern, name))
233 .map(|(name, entry)| (name.as_str(), &entry.kernel_id))
234 .collect()
235 }
236
237 pub fn list_names(&self) -> Vec<&str> {
239 self.entries.keys().map(String::as_str).collect()
240 }
241
242 pub fn len(&self) -> usize {
244 self.entries.len()
245 }
246
247 pub fn is_empty(&self) -> bool {
249 self.entries.is_empty()
250 }
251
252 pub fn watch(&mut self, pattern: impl Into<String>) -> usize {
256 let id = self.watchers.len();
257 self.watchers.push((pattern.into(), Vec::new()));
258 id
259 }
260
261 pub fn drain_events(&mut self, watcher_id: usize) -> Vec<RegistryEvent> {
263 if let Some((_, events)) = self.watchers.get_mut(watcher_id) {
264 std::mem::take(events)
265 } else {
266 Vec::new()
267 }
268 }
269
270 fn notify_watchers(&mut self, event: &RegistryEvent) {
271 let name = match event {
272 RegistryEvent::Registered { name, .. } => name,
273 RegistryEvent::Deregistered { name, .. } => name,
274 RegistryEvent::Updated { name, .. } => name,
275 };
276
277 for (pattern, events) in &mut self.watchers {
278 if wildcard_match(pattern, name) {
279 events.push(event.clone());
280 }
281 }
282 }
283}
284
285impl Default for ActorRegistry {
286 fn default() -> Self {
287 Self::new()
288 }
289}
290
291fn wildcard_match(pattern: &str, text: &str) -> bool {
297 let p = pattern.chars().collect::<Vec<_>>();
298 let t = text.chars().collect::<Vec<_>>();
299
300 wildcard_match_recursive(&p, &t, 0, 0)
301}
302
303fn wildcard_match_recursive(pattern: &[char], text: &[char], pi: usize, ti: usize) -> bool {
304 if pi == pattern.len() && ti == text.len() {
305 return true;
306 }
307 if pi == pattern.len() {
308 return false;
309 }
310
311 if pi + 1 < pattern.len() && pattern[pi] == '*' && pattern[pi + 1] == '*' {
313 for i in ti..=text.len() {
315 if wildcard_match_recursive(pattern, text, pi + 2, i) {
316 return true;
317 }
318 }
319 return false;
320 }
321
322 if pattern[pi] == '*' {
324 for i in ti..=text.len() {
325 if i > ti && i <= text.len() && text[i - 1] == '/' {
326 break;
328 }
329 if wildcard_match_recursive(pattern, text, pi + 1, i) {
330 return true;
331 }
332 }
333 return false;
334 }
335
336 if pattern[pi] == '?' && ti < text.len() {
338 return wildcard_match_recursive(pattern, text, pi + 1, ti + 1);
339 }
340
341 if ti < text.len() && pattern[pi] == text[ti] {
343 return wildcard_match_recursive(pattern, text, pi + 1, ti + 1);
344 }
345
346 false
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn test_register_and_lookup() {
355 let mut reg = ActorRegistry::new();
356
357 reg.register("isa_ontology", KernelId::new("k1"));
358 reg.register("pcaob_rules", KernelId::new("k2"));
359
360 assert_eq!(reg.lookup("isa_ontology"), Some(&KernelId::new("k1")));
361 assert_eq!(reg.lookup("pcaob_rules"), Some(&KernelId::new("k2")));
362 assert_eq!(reg.lookup("nonexistent"), None);
363 }
364
365 #[test]
366 fn test_deregister() {
367 let mut reg = ActorRegistry::new();
368 reg.register("actor_a", KernelId::new("k1"));
369 assert_eq!(reg.len(), 1);
370
371 reg.deregister("actor_a");
372 assert_eq!(reg.len(), 0);
373 assert_eq!(reg.lookup("actor_a"), None);
374 }
375
376 #[test]
377 fn test_update_registration() {
378 let mut reg = ActorRegistry::new();
379 reg.register("my_actor", KernelId::new("k1"));
380 let event = reg.register("my_actor", KernelId::new("k2"));
381
382 assert!(matches!(event, RegistryEvent::Updated { .. }));
383 assert_eq!(reg.lookup("my_actor"), Some(&KernelId::new("k2")));
384 }
385
386 #[test]
387 fn test_reverse_lookup() {
388 let mut reg = ActorRegistry::new();
389 reg.register("name_a", KernelId::new("k1"));
390 reg.register("name_b", KernelId::new("k1"));
391
392 let names = reg.names_for(&KernelId::new("k1"));
393 assert_eq!(names.len(), 2);
394 assert!(names.contains(&"name_a"));
395 assert!(names.contains(&"name_b"));
396 }
397
398 #[test]
399 fn test_wildcard_exact() {
400 assert!(wildcard_match("hello", "hello"));
401 assert!(!wildcard_match("hello", "world"));
402 }
403
404 #[test]
405 fn test_wildcard_star() {
406 assert!(wildcard_match("isa_*", "isa_ontology"));
407 assert!(wildcard_match("isa_*", "isa_rules"));
408 assert!(!wildcard_match("isa_*", "pcaob_rules"));
409 }
410
411 #[test]
412 fn test_wildcard_star_no_slash() {
413 assert!(wildcard_match("standards/*", "standards/isa"));
414 assert!(!wildcard_match("standards/*", "standards/isa/500"));
415 }
416
417 #[test]
418 fn test_wildcard_double_star() {
419 assert!(wildcard_match("standards/**", "standards/isa/500"));
420 assert!(wildcard_match("standards/**", "standards/isa"));
421 assert!(!wildcard_match("standards/**", "other/isa"));
422 }
423
424 #[test]
425 fn test_wildcard_question() {
426 assert!(wildcard_match("actor_?", "actor_a"));
427 assert!(wildcard_match("actor_?", "actor_b"));
428 assert!(!wildcard_match("actor_?", "actor_ab"));
429 }
430
431 #[test]
432 fn test_pattern_lookup() {
433 let mut reg = ActorRegistry::new();
434 reg.register("standards/isa/500", KernelId::new("k1"));
435 reg.register("standards/isa/700", KernelId::new("k2"));
436 reg.register("standards/pcaob/101", KernelId::new("k3"));
437 reg.register("other/thing", KernelId::new("k4"));
438
439 let isa = reg.lookup_pattern("standards/isa/*");
440 assert_eq!(isa.len(), 2);
441
442 let all_standards = reg.lookup_pattern("standards/**");
443 assert_eq!(all_standards.len(), 3);
444 }
445
446 #[test]
447 fn test_watcher() {
448 let mut reg = ActorRegistry::new();
449 let watcher = reg.watch("isa_*");
450
451 reg.register("isa_ontology", KernelId::new("k1"));
452 reg.register("pcaob_rules", KernelId::new("k2")); reg.register("isa_rules", KernelId::new("k3"));
454
455 let events = reg.drain_events(watcher);
456 assert_eq!(events.len(), 2); }
458
459 #[test]
460 fn test_deregister_kernel() {
461 let mut reg = ActorRegistry::new();
462 reg.register("name_a", KernelId::new("k1"));
463 reg.register("name_b", KernelId::new("k1"));
464 reg.register("name_c", KernelId::new("k2"));
465
466 let events = reg.deregister_kernel(&KernelId::new("k1"));
467 assert_eq!(events.len(), 2);
468 assert_eq!(reg.len(), 1);
469 assert_eq!(reg.lookup("name_c"), Some(&KernelId::new("k2")));
470 }
471
472 #[test]
473 fn test_register_with_tags() {
474 let mut reg = ActorRegistry::new();
475 let mut tags = HashMap::new();
476 tags.insert("domain".to_string(), "audit".to_string());
477 tags.insert("version".to_string(), "2.0".to_string());
478
479 reg.register_with_tags("isa_ontology", KernelId::new("k1"), tags);
480
481 let entry = reg.lookup_entry("isa_ontology").unwrap();
482 assert_eq!(entry.tags.get("domain").unwrap(), "audit");
483 }
484}