use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct AgentPath {
segments: Vec<String>,
}
impl AgentPath {
pub fn root() -> Self {
Self {
segments: vec!["root".to_string()],
}
}
pub fn join(&self, name: &str) -> Self {
let mut segments = self.segments.clone();
segments.push(name.to_string());
Self { segments }
}
pub fn parse(path: &str) -> Option<Self> {
if path.is_empty() {
return None;
}
let segments: Vec<String> = path.split('/').map(|s| s.to_string()).collect();
if segments.iter().any(|s| s.is_empty()) {
return None;
}
if segments.first().map(|s| s.as_str()) != Some("root") {
return None;
}
Some(Self { segments })
}
pub fn is_root(&self) -> bool {
self.segments.len() == 1
}
pub fn depth(&self) -> usize {
self.segments.len() - 1
}
pub fn name(&self) -> &str {
self.segments.last().map(|s| s.as_str()).unwrap_or("root")
}
pub fn parent(&self) -> Option<Self> {
if self.is_root() {
return None;
}
let mut segments = self.segments.clone();
segments.pop();
Some(Self { segments })
}
pub fn segments(&self) -> &[String] {
&self.segments
}
}
impl fmt::Display for AgentPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.segments.join("/"))
}
}
impl std::str::FromStr for AgentPath {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
AgentPath::parse(s).ok_or_else(|| format!("invalid agent path: '{}'", s))
}
}
impl serde::Serialize for AgentPath {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
impl<'de> serde::Deserialize<'de> for AgentPath {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
AgentPath::parse(&s)
.ok_or_else(|| serde::de::Error::custom(format!("invalid agent path: '{}'", s)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn root_path() {
let root = AgentPath::root();
assert!(root.is_root());
assert_eq!(root.depth(), 0);
assert_eq!(root.name(), "root");
assert_eq!(root.to_string(), "root");
assert_eq!(root.parent(), None);
}
#[test]
fn join_child() {
let root = AgentPath::root();
let child = root.join("searcher");
assert!(!child.is_root());
assert_eq!(child.depth(), 1);
assert_eq!(child.name(), "searcher");
assert_eq!(child.to_string(), "root/searcher");
assert_eq!(child.parent(), Some(root));
}
#[test]
fn join_nested() {
let root = AgentPath::root();
let grandchild = root.join("searcher").join("worker-1");
assert!(!grandchild.is_root());
assert_eq!(grandchild.depth(), 2);
assert_eq!(grandchild.name(), "worker-1");
assert_eq!(grandchild.to_string(), "root/searcher/worker-1");
assert_eq!(grandchild.parent(), Some(root.join("searcher")));
}
#[test]
fn parse_valid() {
assert_eq!("root".parse::<AgentPath>().unwrap(), AgentPath::root());
assert_eq!(
"root/searcher".parse::<AgentPath>().unwrap(),
AgentPath::root().join("searcher")
);
assert_eq!(
"root/a/b".parse::<AgentPath>().unwrap(),
AgentPath::root().join("a").join("b")
);
}
#[test]
fn parse_invalid() {
assert!("".parse::<AgentPath>().is_err());
assert!("/root".parse::<AgentPath>().is_err()); assert!("not-root/a".parse::<AgentPath>().is_err()); assert!("root/".parse::<AgentPath>().is_err()); assert!("root//a".parse::<AgentPath>().is_err()); }
#[test]
fn display_roundtrip() {
let paths = vec!["root", "root/searcher", "root/a/b"];
for s in paths {
let parsed: AgentPath = s.parse().unwrap();
assert_eq!(parsed.to_string(), s);
}
}
#[test]
fn serde_roundtrip() {
let path = AgentPath::root().join("searcher");
let json = serde_json::to_string(&path).unwrap();
assert_eq!(json, "\"root/searcher\"");
let deserialized: AgentPath = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, path);
}
#[test]
fn serde_invalid() {
assert!(serde_json::from_str::<AgentPath>("\"\"").is_err());
assert!(serde_json::from_str::<AgentPath>("\"not-root\"").is_err());
}
#[test]
fn segments_accessor() {
let path = AgentPath::root().join("a").join("b");
assert_eq!(path.segments(), &["root", "a", "b"]);
}
#[test]
fn ordering() {
let a = AgentPath::root().join("a");
let b = AgentPath::root().join("b");
let a1 = AgentPath::root().join("a").join("1");
assert!(a < b);
assert!(a < a1);
}
}