agent_works/multi_agent/
path.rs1use std::fmt;
9
10#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
36pub struct AgentPath {
37 segments: Vec<String>,
40}
41
42impl AgentPath {
43 pub fn root() -> Self {
45 Self {
46 segments: vec!["root".to_string()],
47 }
48 }
49
50 pub fn join(&self, name: &str) -> Self {
62 let mut segments = self.segments.clone();
63 segments.push(name.to_string());
64 Self { segments }
65 }
66
67 pub fn parse(path: &str) -> Option<Self> {
72 if path.is_empty() {
73 return None;
74 }
75 let segments: Vec<String> = path.split('/').map(|s| s.to_string()).collect();
76 if segments.iter().any(|s| s.is_empty()) {
78 return None;
79 }
80 if segments.first().map(|s| s.as_str()) != Some("root") {
82 return None;
83 }
84 Some(Self { segments })
85 }
86
87 pub fn is_root(&self) -> bool {
89 self.segments.len() == 1
90 }
91
92 pub fn depth(&self) -> usize {
96 self.segments.len() - 1
97 }
98
99 pub fn name(&self) -> &str {
111 self.segments.last().map(|s| s.as_str()).unwrap_or("root")
112 }
113
114 pub fn parent(&self) -> Option<Self> {
126 if self.is_root() {
127 return None;
128 }
129 let mut segments = self.segments.clone();
130 segments.pop();
131 Some(Self { segments })
132 }
133
134 pub fn segments(&self) -> &[String] {
136 &self.segments
137 }
138}
139
140impl fmt::Display for AgentPath {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 write!(f, "{}", self.segments.join("/"))
143 }
144}
145
146impl std::str::FromStr for AgentPath {
147 type Err = String;
148
149 fn from_str(s: &str) -> Result<Self, Self::Err> {
150 AgentPath::parse(s).ok_or_else(|| format!("invalid agent path: '{}'", s))
151 }
152}
153
154impl serde::Serialize for AgentPath {
155 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
156 serializer.serialize_str(&self.to_string())
157 }
158}
159
160impl<'de> serde::Deserialize<'de> for AgentPath {
161 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
162 let s = String::deserialize(deserializer)?;
163 AgentPath::parse(&s)
164 .ok_or_else(|| serde::de::Error::custom(format!("invalid agent path: '{}'", s)))
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn root_path() {
174 let root = AgentPath::root();
175 assert!(root.is_root());
176 assert_eq!(root.depth(), 0);
177 assert_eq!(root.name(), "root");
178 assert_eq!(root.to_string(), "root");
179 assert_eq!(root.parent(), None);
180 }
181
182 #[test]
183 fn join_child() {
184 let root = AgentPath::root();
185 let child = root.join("searcher");
186 assert!(!child.is_root());
187 assert_eq!(child.depth(), 1);
188 assert_eq!(child.name(), "searcher");
189 assert_eq!(child.to_string(), "root/searcher");
190 assert_eq!(child.parent(), Some(root));
191 }
192
193 #[test]
194 fn join_nested() {
195 let root = AgentPath::root();
196 let grandchild = root.join("searcher").join("worker-1");
197 assert!(!grandchild.is_root());
198 assert_eq!(grandchild.depth(), 2);
199 assert_eq!(grandchild.name(), "worker-1");
200 assert_eq!(grandchild.to_string(), "root/searcher/worker-1");
201 assert_eq!(grandchild.parent(), Some(root.join("searcher")));
202 }
203
204 #[test]
205 fn parse_valid() {
206 assert_eq!("root".parse::<AgentPath>().unwrap(), AgentPath::root());
207 assert_eq!(
208 "root/searcher".parse::<AgentPath>().unwrap(),
209 AgentPath::root().join("searcher")
210 );
211 assert_eq!(
212 "root/a/b".parse::<AgentPath>().unwrap(),
213 AgentPath::root().join("a").join("b")
214 );
215 }
216
217 #[test]
218 fn parse_invalid() {
219 assert!("".parse::<AgentPath>().is_err());
220 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()); }
225
226 #[test]
227 fn display_roundtrip() {
228 let paths = vec!["root", "root/searcher", "root/a/b"];
229 for s in paths {
230 let parsed: AgentPath = s.parse().unwrap();
231 assert_eq!(parsed.to_string(), s);
232 }
233 }
234
235 #[test]
236 fn serde_roundtrip() {
237 let path = AgentPath::root().join("searcher");
238 let json = serde_json::to_string(&path).unwrap();
239 assert_eq!(json, "\"root/searcher\"");
240 let deserialized: AgentPath = serde_json::from_str(&json).unwrap();
241 assert_eq!(deserialized, path);
242 }
243
244 #[test]
245 fn serde_invalid() {
246 assert!(serde_json::from_str::<AgentPath>("\"\"").is_err());
247 assert!(serde_json::from_str::<AgentPath>("\"not-root\"").is_err());
248 }
249
250 #[test]
251 fn segments_accessor() {
252 let path = AgentPath::root().join("a").join("b");
253 assert_eq!(path.segments(), &["root", "a", "b"]);
254 }
255
256 #[test]
257 fn ordering() {
258 let a = AgentPath::root().join("a");
259 let b = AgentPath::root().join("b");
260 let a1 = AgentPath::root().join("a").join("1");
261 assert!(a < b);
262 assert!(a < a1);
263 }
264}