use agent_mesh_protocol::Fingerprint;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Topic {
pub user_fp: Fingerprint,
pub name: String,
}
impl Topic {
#[must_use]
pub fn new(user_fp: Fingerprint, name: impl Into<String>) -> Self {
Self {
user_fp,
name: name.into(),
}
}
#[must_use]
pub fn wire(&self) -> String {
format!("{}:{}", self.user_fp.hex(), self.name)
}
#[must_use]
pub fn parse_wire(s: &str) -> Option<Self> {
let (fp_str, name) = s.split_once(':')?;
let user_fp = Fingerprint::from_str(fp_str).ok()?;
Some(Self {
user_fp,
name: name.to_string(),
})
}
}
impl fmt::Display for Topic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.wire())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fp_of(seed: u8) -> Fingerprint {
Fingerprint([seed; 32])
}
#[test]
fn wire_format_roundtrip() {
let t = Topic::new(fp_of(1), "echo");
let s = t.wire();
let back = Topic::parse_wire(&s).expect("parse roundtrip");
assert_eq!(back, t);
}
#[test]
fn different_users_different_wire() {
let a = Topic::new(fp_of(1), "echo");
let b = Topic::new(fp_of(2), "echo");
assert_ne!(a.wire(), b.wire());
assert!(a.wire().ends_with(":echo"));
assert!(b.wire().ends_with(":echo"));
}
#[test]
fn parse_rejects_malformed() {
assert!(Topic::parse_wire("notopic").is_none());
assert!(Topic::parse_wire(":echo").is_none());
assert!(Topic::parse_wire("zz:echo").is_none());
}
#[test]
fn display_matches_wire() {
let t = Topic::new(fp_of(7), "drake/work");
assert_eq!(format!("{t}"), t.wire());
}
#[test]
fn name_may_contain_colons() {
let t = Topic::new(fp_of(3), "namespace:sub");
let back = Topic::parse_wire(&t.wire()).expect("parse");
assert_eq!(back.name, "namespace:sub");
}
#[test]
fn empty_name_is_allowed() {
let t = Topic::new(fp_of(4), "");
let back = Topic::parse_wire(&t.wire()).expect("parse");
assert_eq!(back.name, "");
}
#[test]
fn topic_hashes_with_user_and_name() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(Topic::new(fp_of(1), "echo"));
set.insert(Topic::new(fp_of(1), "echo"));
set.insert(Topic::new(fp_of(2), "echo"));
set.insert(Topic::new(fp_of(1), "other"));
assert_eq!(set.len(), 3);
}
}