use std::collections::{HashMap, HashSet};
use uuid::Uuid;
use crate::identity::AgentIdentity;
use crate::transport::TransportAddress;
#[derive(Debug, Default)]
pub struct PeerTable {
peers: HashMap<Uuid, AgentIdentity>,
addresses: HashMap<Uuid, Vec<TransportAddress>>,
subscriptions: HashMap<String, HashSet<Uuid>>,
}
impl PeerTable {
pub fn new() -> Self {
Self::default()
}
pub fn upsert(&mut self, identity: AgentIdentity, addresses: Vec<TransportAddress>) {
let id = identity.id;
self.peers.insert(id, identity);
self.addresses.insert(id, addresses);
}
pub fn remove(&mut self, id: &Uuid) -> Option<AgentIdentity> {
self.addresses.remove(id);
for subs in self.subscriptions.values_mut() {
subs.remove(id);
}
self.peers.remove(id)
}
pub fn get(&self, id: &Uuid) -> Option<&AgentIdentity> {
self.peers.get(id)
}
pub fn addresses(&self, id: &Uuid) -> Option<&[TransportAddress]> {
self.addresses.get(id).map(|v| v.as_slice())
}
pub fn all_peers(&self) -> impl Iterator<Item = &AgentIdentity> {
self.peers.values()
}
pub fn all_peer_ids(&self) -> impl Iterator<Item = &Uuid> {
self.peers.keys()
}
pub fn len(&self) -> usize {
self.peers.len()
}
pub fn is_empty(&self) -> bool {
self.peers.is_empty()
}
pub fn subscribe(&mut self, peer_id: Uuid, topic: impl Into<String>) {
self.subscriptions
.entry(topic.into())
.or_default()
.insert(peer_id);
}
pub fn unsubscribe(&mut self, peer_id: &Uuid, topic: &str) {
if let Some(subs) = self.subscriptions.get_mut(topic) {
subs.remove(peer_id);
if subs.is_empty() {
self.subscriptions.remove(topic);
}
}
}
pub fn subscribers(&self, topic: &str) -> Vec<Uuid> {
self.subscriptions
.get(topic)
.map(|s| s.iter().copied().collect())
.unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_peer(name: &str) -> (AgentIdentity, Vec<TransportAddress>) {
let identity = AgentIdentity::new(name);
let addrs = vec![TransportAddress::Tcp("127.0.0.1:9090".parse().unwrap())];
(identity, addrs)
}
#[test]
fn upsert_and_get() {
let mut table = PeerTable::new();
let (identity, addrs) = make_peer("agent-a");
let id = identity.id;
table.upsert(identity, addrs);
assert_eq!(table.len(), 1);
assert!(!table.is_empty());
assert!(table.get(&id).is_some());
assert_eq!(table.get(&id).unwrap().name, "agent-a");
assert_eq!(table.addresses(&id).unwrap().len(), 1);
}
#[test]
fn remove_peer() {
let mut table = PeerTable::new();
let (identity, addrs) = make_peer("agent-b");
let id = identity.id;
table.upsert(identity, addrs);
let removed = table.remove(&id);
assert!(removed.is_some());
assert_eq!(table.len(), 0);
assert!(table.get(&id).is_none());
}
#[test]
fn topic_subscriptions() {
let mut table = PeerTable::new();
let (id_a, addrs_a) = make_peer("a");
let (id_b, addrs_b) = make_peer("b");
let uuid_a = id_a.id;
let uuid_b = id_b.id;
table.upsert(id_a, addrs_a);
table.upsert(id_b, addrs_b);
table.subscribe(uuid_a, "status");
table.subscribe(uuid_b, "status");
table.subscribe(uuid_a, "errors");
assert_eq!(table.subscribers("status").len(), 2);
assert_eq!(table.subscribers("errors").len(), 1);
assert!(table.subscribers("unknown").is_empty());
table.unsubscribe(&uuid_a, "status");
assert_eq!(table.subscribers("status").len(), 1);
}
#[test]
fn remove_peer_cleans_subscriptions() {
let mut table = PeerTable::new();
let (identity, addrs) = make_peer("agent-c");
let id = identity.id;
table.upsert(identity, addrs);
table.subscribe(id, "events");
assert_eq!(table.subscribers("events").len(), 1);
table.remove(&id);
assert!(table.subscribers("events").is_empty());
}
}