mcp_servers/
workspace_paths.rs1use std::path::{Path, PathBuf};
2
3pub fn current_dir() -> PathBuf {
4 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
5}
6
7pub fn resolve_path(root: &Path, path: PathBuf) -> PathBuf {
9 if path.is_absolute() { path } else { root.join(path) }
10}
11
12pub fn resolve_root(base: &Path, root: Option<PathBuf>) -> PathBuf {
13 root.map_or_else(|| base.to_path_buf(), |path| resolve_path(base, path))
14}
15
16pub fn relative_path(root: &Path, path: &str) -> String {
17 Path::new(path)
18 .strip_prefix(root)
19 .map_or_else(|_| path.to_string(), |relative| relative.to_string_lossy().to_string())
20}
21
22#[derive(Debug, thiserror::Error)]
23#[error("file path is required and cannot be empty")]
24pub struct EmptyFilePathError;
25
26pub fn resolve_file(root: &Path, raw: &str) -> Result<PathBuf, EmptyFilePathError> {
30 if raw.trim().is_empty() {
31 return Err(EmptyFilePathError);
32 }
33 Ok(resolve_path(root, PathBuf::from(raw)))
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39
40 #[test]
41 fn relative_path_strips_root_prefix() {
42 assert_eq!(relative_path(Path::new("/workspace"), "/workspace/src/main.rs"), "src/main.rs");
43 assert_eq!(relative_path(Path::new("/workspace"), "/elsewhere/lib.rs"), "/elsewhere/lib.rs");
44 }
45
46 #[test]
47 fn resolves_relative_path_against_root() {
48 assert_eq!(
49 resolve_path(Path::new("/workspace"), PathBuf::from("src/main.rs")),
50 PathBuf::from("/workspace/src/main.rs")
51 );
52 }
53
54 #[test]
55 fn root_override_resolves_against_base() {
56 assert_eq!(resolve_root(Path::new("/workspace"), None), PathBuf::from("/workspace"));
57 assert_eq!(resolve_root(Path::new("/workspace"), Some("sub".into())), PathBuf::from("/workspace/sub"));
58 assert_eq!(resolve_root(Path::new("/workspace"), Some("/abs".into())), PathBuf::from("/abs"));
59 }
60
61 #[test]
62 fn leaves_absolute_path_untouched() {
63 assert_eq!(resolve_path(Path::new("/workspace"), PathBuf::from("/etc/passwd")), PathBuf::from("/etc/passwd"));
64 }
65
66 #[test]
67 fn preserves_non_empty_file_path_text() {
68 let resolved = resolve_file(Path::new("/workspace"), " leading space.txt ").unwrap();
69 assert_eq!(resolved, PathBuf::from("/workspace/ leading space.txt "));
70 }
71
72 #[test]
73 fn rejects_whitespace_only_required_file_path() {
74 let err = resolve_file(Path::new("/workspace"), " \t ").unwrap_err();
75 assert!(err.to_string().contains("file path is required"));
76 }
77}