vtcode_commons/
paths.rs

1use std::path::{Path, PathBuf};
2
3/// Provides the root directories an application uses to store data.
4pub trait WorkspacePaths: Send + Sync {
5    /// Absolute path to the application's workspace root.
6    fn workspace_root(&self) -> &Path;
7
8    /// Returns the directory where configuration files should be stored.
9    fn config_dir(&self) -> PathBuf;
10
11    /// Returns an optional cache directory for transient data.
12    fn cache_dir(&self) -> Option<PathBuf> {
13        None
14    }
15
16    /// Returns an optional directory for telemetry or log artifacts.
17    fn telemetry_dir(&self) -> Option<PathBuf> {
18        None
19    }
20
21    /// Determine the [`PathScope`] for a given path based on workspace directories.
22    ///
23    /// Returns the most specific scope matching the path:
24    /// - `Workspace` if under `workspace_root()`
25    /// - `Config` if under `config_dir()`
26    /// - `Cache` if under `cache_dir()`
27    /// - `Telemetry` if under `telemetry_dir()`
28    /// - Falls back to `Cache` if no match
29    fn scope_for_path(&self, path: &Path) -> PathScope {
30        if path.starts_with(self.workspace_root()) {
31            return PathScope::Workspace;
32        }
33
34        let config_dir = self.config_dir();
35        if path.starts_with(&config_dir) {
36            return PathScope::Config;
37        }
38
39        if let Some(cache_dir) = self.cache_dir() {
40            if path.starts_with(&cache_dir) {
41                return PathScope::Cache;
42            }
43        }
44
45        if let Some(telemetry_dir) = self.telemetry_dir() {
46            if path.starts_with(&telemetry_dir) {
47                return PathScope::Telemetry;
48            }
49        }
50
51        PathScope::Cache
52    }
53}
54
55/// Helper trait that adds path resolution helpers on top of [`WorkspacePaths`].
56pub trait PathResolver: WorkspacePaths {
57    /// Resolve a path relative to the workspace root.
58    fn resolve<P>(&self, relative: P) -> PathBuf
59    where
60        P: AsRef<Path>,
61    {
62        self.workspace_root().join(relative)
63    }
64
65    /// Resolve a path within the configuration directory.
66    fn resolve_config<P>(&self, relative: P) -> PathBuf
67    where
68        P: AsRef<Path>,
69    {
70        self.config_dir().join(relative)
71    }
72}
73
74impl<T> PathResolver for T where T: WorkspacePaths + ?Sized {}
75
76/// Enumeration describing the conceptual scope of a file path.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum PathScope {
79    Workspace,
80    Config,
81    Cache,
82    Telemetry,
83}
84
85impl PathScope {
86    /// Returns a human-readable description used in error messages.
87    pub fn description(self) -> &'static str {
88        match self {
89            Self::Workspace => "workspace",
90            Self::Config => "configuration",
91            Self::Cache => "cache",
92            Self::Telemetry => "telemetry",
93        }
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use std::path::PathBuf;
101
102    struct StaticPaths {
103        root: PathBuf,
104        config: PathBuf,
105    }
106
107    impl WorkspacePaths for StaticPaths {
108        fn workspace_root(&self) -> &Path {
109            &self.root
110        }
111
112        fn config_dir(&self) -> PathBuf {
113            self.config.clone()
114        }
115
116        fn cache_dir(&self) -> Option<PathBuf> {
117            Some(self.root.join("cache"))
118        }
119    }
120
121    #[test]
122    fn resolves_relative_paths() {
123        let paths = StaticPaths {
124            root: PathBuf::from("/tmp/project"),
125            config: PathBuf::from("/tmp/project/config"),
126        };
127
128        assert_eq!(
129            PathResolver::resolve(&paths, "subdir/file.txt"),
130            PathBuf::from("/tmp/project/subdir/file.txt")
131        );
132        assert_eq!(
133            PathResolver::resolve_config(&paths, "settings.toml"),
134            PathBuf::from("/tmp/project/config/settings.toml")
135        );
136        assert_eq!(paths.cache_dir(), Some(PathBuf::from("/tmp/project/cache")));
137    }
138}