use std::collections::HashMap;
use std::time::Instant;
use crate::runtime::KernelId;
#[derive(Debug, Clone)]
pub struct RegistryEntry {
pub name: String,
pub kernel_id: KernelId,
pub registered_at: Instant,
pub tags: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub enum RegistryEvent {
Registered {
name: String,
kernel_id: KernelId,
},
Deregistered {
name: String,
kernel_id: KernelId,
},
Updated {
name: String,
old_kernel_id: KernelId,
new_kernel_id: KernelId,
},
}
pub struct ActorRegistry {
entries: HashMap<String, RegistryEntry>,
reverse: HashMap<KernelId, Vec<String>>,
watchers: Vec<(String, Vec<RegistryEvent>)>,
}
impl ActorRegistry {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
reverse: HashMap::new(),
watchers: Vec::new(),
}
}
pub fn register(&mut self, name: impl Into<String>, kernel_id: KernelId) -> RegistryEvent {
let name = name.into();
let event = if let Some(existing) = self.entries.get(&name) {
let old_id = existing.kernel_id.clone();
if let Some(names) = self.reverse.get_mut(&old_id) {
names.retain(|n| n != &name);
}
RegistryEvent::Updated {
name: name.clone(),
old_kernel_id: old_id,
new_kernel_id: kernel_id.clone(),
}
} else {
RegistryEvent::Registered {
name: name.clone(),
kernel_id: kernel_id.clone(),
}
};
self.reverse
.entry(kernel_id.clone())
.or_default()
.push(name.clone());
self.entries.insert(
name.clone(),
RegistryEntry {
name,
kernel_id,
registered_at: Instant::now(),
tags: HashMap::new(),
},
);
self.notify_watchers(&event);
event
}
pub fn register_with_tags(
&mut self,
name: impl Into<String>,
kernel_id: KernelId,
tags: HashMap<String, String>,
) -> RegistryEvent {
let event = self.register(name, kernel_id);
if let RegistryEvent::Registered { ref name, .. }
| RegistryEvent::Updated { ref name, .. } = event
{
if let Some(entry) = self.entries.get_mut(name) {
entry.tags = tags;
}
}
event
}
pub fn deregister(&mut self, name: &str) -> Option<RegistryEvent> {
if let Some(entry) = self.entries.remove(name) {
if let Some(names) = self.reverse.get_mut(&entry.kernel_id) {
names.retain(|n| n != name);
if names.is_empty() {
self.reverse.remove(&entry.kernel_id);
}
}
let event = RegistryEvent::Deregistered {
name: name.to_string(),
kernel_id: entry.kernel_id,
};
self.notify_watchers(&event);
Some(event)
} else {
None
}
}
pub fn deregister_kernel(&mut self, kernel_id: &KernelId) -> Vec<RegistryEvent> {
let mut events = Vec::new();
if let Some(names) = self.reverse.remove(kernel_id) {
for name in names {
if let Some(entry) = self.entries.remove(&name) {
events.push(RegistryEvent::Deregistered {
name,
kernel_id: entry.kernel_id,
});
}
}
}
for event in &events {
self.notify_watchers(event);
}
events
}
pub fn lookup(&self, name: &str) -> Option<&KernelId> {
self.entries.get(name).map(|e| &e.kernel_id)
}
pub fn lookup_entry(&self, name: &str) -> Option<&RegistryEntry> {
self.entries.get(name)
}
pub fn names_for(&self, kernel_id: &KernelId) -> Vec<&str> {
self.reverse
.get(kernel_id)
.map(|names| names.iter().map(String::as_str).collect())
.unwrap_or_default()
}
pub fn lookup_pattern(&self, pattern: &str) -> Vec<(&str, &KernelId)> {
self.entries
.iter()
.filter(|(name, _)| wildcard_match(pattern, name))
.map(|(name, entry)| (name.as_str(), &entry.kernel_id))
.collect()
}
pub fn list_names(&self) -> Vec<&str> {
self.entries.keys().map(String::as_str).collect()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn watch(&mut self, pattern: impl Into<String>) -> usize {
let id = self.watchers.len();
self.watchers.push((pattern.into(), Vec::new()));
id
}
pub fn drain_events(&mut self, watcher_id: usize) -> Vec<RegistryEvent> {
if let Some((_, events)) = self.watchers.get_mut(watcher_id) {
std::mem::take(events)
} else {
Vec::new()
}
}
fn notify_watchers(&mut self, event: &RegistryEvent) {
let name = match event {
RegistryEvent::Registered { name, .. } => name,
RegistryEvent::Deregistered { name, .. } => name,
RegistryEvent::Updated { name, .. } => name,
};
for (pattern, events) in &mut self.watchers {
if wildcard_match(pattern, name) {
events.push(event.clone());
}
}
}
}
impl Default for ActorRegistry {
fn default() -> Self {
Self::new()
}
}
fn wildcard_match(pattern: &str, text: &str) -> bool {
let p = pattern.chars().collect::<Vec<_>>();
let t = text.chars().collect::<Vec<_>>();
wildcard_match_recursive(&p, &t, 0, 0)
}
fn wildcard_match_recursive(pattern: &[char], text: &[char], pi: usize, ti: usize) -> bool {
if pi == pattern.len() && ti == text.len() {
return true;
}
if pi == pattern.len() {
return false;
}
if pi + 1 < pattern.len() && pattern[pi] == '*' && pattern[pi + 1] == '*' {
for i in ti..=text.len() {
if wildcard_match_recursive(pattern, text, pi + 2, i) {
return true;
}
}
return false;
}
if pattern[pi] == '*' {
for i in ti..=text.len() {
if i > ti && i <= text.len() && text[i - 1] == '/' {
break;
}
if wildcard_match_recursive(pattern, text, pi + 1, i) {
return true;
}
}
return false;
}
if pattern[pi] == '?' && ti < text.len() {
return wildcard_match_recursive(pattern, text, pi + 1, ti + 1);
}
if ti < text.len() && pattern[pi] == text[ti] {
return wildcard_match_recursive(pattern, text, pi + 1, ti + 1);
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_register_and_lookup() {
let mut reg = ActorRegistry::new();
reg.register("isa_ontology", KernelId::new("k1"));
reg.register("pcaob_rules", KernelId::new("k2"));
assert_eq!(reg.lookup("isa_ontology"), Some(&KernelId::new("k1")));
assert_eq!(reg.lookup("pcaob_rules"), Some(&KernelId::new("k2")));
assert_eq!(reg.lookup("nonexistent"), None);
}
#[test]
fn test_deregister() {
let mut reg = ActorRegistry::new();
reg.register("actor_a", KernelId::new("k1"));
assert_eq!(reg.len(), 1);
reg.deregister("actor_a");
assert_eq!(reg.len(), 0);
assert_eq!(reg.lookup("actor_a"), None);
}
#[test]
fn test_update_registration() {
let mut reg = ActorRegistry::new();
reg.register("my_actor", KernelId::new("k1"));
let event = reg.register("my_actor", KernelId::new("k2"));
assert!(matches!(event, RegistryEvent::Updated { .. }));
assert_eq!(reg.lookup("my_actor"), Some(&KernelId::new("k2")));
}
#[test]
fn test_reverse_lookup() {
let mut reg = ActorRegistry::new();
reg.register("name_a", KernelId::new("k1"));
reg.register("name_b", KernelId::new("k1"));
let names = reg.names_for(&KernelId::new("k1"));
assert_eq!(names.len(), 2);
assert!(names.contains(&"name_a"));
assert!(names.contains(&"name_b"));
}
#[test]
fn test_wildcard_exact() {
assert!(wildcard_match("hello", "hello"));
assert!(!wildcard_match("hello", "world"));
}
#[test]
fn test_wildcard_star() {
assert!(wildcard_match("isa_*", "isa_ontology"));
assert!(wildcard_match("isa_*", "isa_rules"));
assert!(!wildcard_match("isa_*", "pcaob_rules"));
}
#[test]
fn test_wildcard_star_no_slash() {
assert!(wildcard_match("standards/*", "standards/isa"));
assert!(!wildcard_match("standards/*", "standards/isa/500"));
}
#[test]
fn test_wildcard_double_star() {
assert!(wildcard_match("standards/**", "standards/isa/500"));
assert!(wildcard_match("standards/**", "standards/isa"));
assert!(!wildcard_match("standards/**", "other/isa"));
}
#[test]
fn test_wildcard_question() {
assert!(wildcard_match("actor_?", "actor_a"));
assert!(wildcard_match("actor_?", "actor_b"));
assert!(!wildcard_match("actor_?", "actor_ab"));
}
#[test]
fn test_pattern_lookup() {
let mut reg = ActorRegistry::new();
reg.register("standards/isa/500", KernelId::new("k1"));
reg.register("standards/isa/700", KernelId::new("k2"));
reg.register("standards/pcaob/101", KernelId::new("k3"));
reg.register("other/thing", KernelId::new("k4"));
let isa = reg.lookup_pattern("standards/isa/*");
assert_eq!(isa.len(), 2);
let all_standards = reg.lookup_pattern("standards/**");
assert_eq!(all_standards.len(), 3);
}
#[test]
fn test_watcher() {
let mut reg = ActorRegistry::new();
let watcher = reg.watch("isa_*");
reg.register("isa_ontology", KernelId::new("k1"));
reg.register("pcaob_rules", KernelId::new("k2")); reg.register("isa_rules", KernelId::new("k3"));
let events = reg.drain_events(watcher);
assert_eq!(events.len(), 2); }
#[test]
fn test_deregister_kernel() {
let mut reg = ActorRegistry::new();
reg.register("name_a", KernelId::new("k1"));
reg.register("name_b", KernelId::new("k1"));
reg.register("name_c", KernelId::new("k2"));
let events = reg.deregister_kernel(&KernelId::new("k1"));
assert_eq!(events.len(), 2);
assert_eq!(reg.len(), 1);
assert_eq!(reg.lookup("name_c"), Some(&KernelId::new("k2")));
}
#[test]
fn test_register_with_tags() {
let mut reg = ActorRegistry::new();
let mut tags = HashMap::new();
tags.insert("domain".to_string(), "audit".to_string());
tags.insert("version".to_string(), "2.0".to_string());
reg.register_with_tags("isa_ontology", KernelId::new("k1"), tags);
let entry = reg.lookup_entry("isa_ontology").unwrap();
assert_eq!(entry.tags.get("domain").unwrap(), "audit");
}
}