agentroot_core/config/
virtual_path.rs1use crate::error::{AgentRootError, Result};
4use crate::VIRTUAL_PATH_PREFIX;
5use std::collections::HashMap;
6use std::path::PathBuf;
7
8pub fn is_virtual_path(path: &str) -> bool {
10 path.starts_with(VIRTUAL_PATH_PREFIX)
11}
12
13pub fn normalize_virtual_path(path: &str) -> String {
15 if !is_virtual_path(path) {
16 return path.to_string();
17 }
18
19 let rest = &path[VIRTUAL_PATH_PREFIX.len()..];
20 let parts: Vec<&str> = rest.splitn(2, '/').collect();
21
22 if parts.is_empty() {
23 return path.to_string();
24 }
25
26 let collection = parts[0].to_lowercase();
27 let file_path = parts.get(1).unwrap_or(&"");
28
29 let normalized_path = file_path
31 .replace('\\', "/")
32 .split('/')
33 .filter(|s| !s.is_empty() && *s != ".")
34 .collect::<Vec<_>>()
35 .join("/");
36
37 format!("{}{}/{}", VIRTUAL_PATH_PREFIX, collection, normalized_path)
38}
39
40pub fn parse_virtual_path(vpath: &str) -> Result<(String, String)> {
42 if !is_virtual_path(vpath) {
43 return Err(AgentRootError::InvalidVirtualPath(format!(
44 "Not a virtual path: {}",
45 vpath
46 )));
47 }
48
49 let rest = &vpath[VIRTUAL_PATH_PREFIX.len()..];
50 let parts: Vec<&str> = rest.splitn(2, '/').collect();
51
52 if parts.is_empty() || parts[0].is_empty() {
53 return Err(AgentRootError::InvalidVirtualPath(format!(
54 "Missing collection in virtual path: {}",
55 vpath
56 )));
57 }
58
59 let collection = parts[0].to_string();
60 let path = parts.get(1).map(|s| s.to_string()).unwrap_or_default();
61
62 Ok((collection, path))
63}
64
65pub fn build_virtual_path(collection: &str, path: &str) -> String {
67 let path = path.trim_start_matches('/');
68 format!("{}{}/{}", VIRTUAL_PATH_PREFIX, collection, path)
69}
70
71pub fn to_virtual_path(
73 abs_path: &str,
74 collection_name: &str,
75 collection_root: &str,
76) -> Result<String> {
77 let abs = std::path::Path::new(abs_path);
78 let root = std::path::Path::new(collection_root);
79
80 let rel_path = abs.strip_prefix(root).map_err(|_| {
81 AgentRootError::InvalidVirtualPath(format!(
82 "Path {} is not under collection root {}",
83 abs_path, collection_root
84 ))
85 })?;
86
87 let rel_str = rel_path.to_string_lossy();
88 Ok(build_virtual_path(collection_name, &rel_str))
89}
90
91pub fn resolve_virtual_path(
93 vpath: &str,
94 collections: &HashMap<String, PathBuf>,
95) -> Result<PathBuf> {
96 let (collection, path) = parse_virtual_path(vpath)?;
97
98 let collection_root = collections
99 .get(&collection)
100 .ok_or_else(|| AgentRootError::CollectionNotFound(collection.clone()))?;
101
102 Ok(collection_root.join(path))
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_is_virtual_path() {
111 assert!(is_virtual_path("agentroot://docs/readme.md"));
112 assert!(!is_virtual_path("/home/user/docs/readme.md"));
113 assert!(!is_virtual_path("docs/readme.md"));
114 }
115
116 #[test]
117 fn test_parse_virtual_path() {
118 let (coll, path) = parse_virtual_path("agentroot://docs/2024/notes.md").unwrap();
119 assert_eq!(coll, "docs");
120 assert_eq!(path, "2024/notes.md");
121 }
122
123 #[test]
124 fn test_build_virtual_path() {
125 assert_eq!(
126 build_virtual_path("docs", "2024/notes.md"),
127 "agentroot://docs/2024/notes.md"
128 );
129 }
130
131 #[test]
132 fn test_normalize_virtual_path() {
133 assert_eq!(
134 normalize_virtual_path("agentroot://DOCS/./foo//bar.md"),
135 "agentroot://docs/foo/bar.md"
136 );
137 }
138}