agentroot_core/config/
virtual_path.rs

1//! Virtual path utilities for agentroot:// URIs
2
3use crate::error::{AgentRootError, Result};
4use crate::VIRTUAL_PATH_PREFIX;
5use std::collections::HashMap;
6use std::path::PathBuf;
7
8/// Check if a string is a virtual path
9pub fn is_virtual_path(path: &str) -> bool {
10    path.starts_with(VIRTUAL_PATH_PREFIX)
11}
12
13/// Normalize a virtual path (lowercase collection, normalize slashes)
14pub 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    // Normalize path separators
30    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
40/// Parse a virtual path into (collection, path) tuple
41pub 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
65/// Build a virtual path from collection and path
66pub 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
71/// Convert an absolute path to a virtual path
72pub 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
91/// Resolve a virtual path to an absolute filesystem path
92pub 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}