1use std::path::{Path, PathBuf};
2
3pub trait WorkspacePaths: Send + Sync {
5 fn workspace_root(&self) -> &Path;
7
8 fn config_dir(&self) -> PathBuf;
10
11 fn cache_dir(&self) -> Option<PathBuf> {
13 None
14 }
15
16 fn telemetry_dir(&self) -> Option<PathBuf> {
18 None
19 }
20}
21
22pub trait PathResolver: WorkspacePaths {
24 fn resolve<P>(&self, relative: P) -> PathBuf
26 where
27 P: AsRef<Path>,
28 {
29 self.workspace_root().join(relative)
30 }
31
32 fn resolve_config<P>(&self, relative: P) -> PathBuf
34 where
35 P: AsRef<Path>,
36 {
37 self.config_dir().join(relative)
38 }
39}
40
41impl<T> PathResolver for T where T: WorkspacePaths + ?Sized {}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum PathScope {
46 Workspace,
47 Config,
48 Cache,
49 Telemetry,
50}
51
52impl PathScope {
53 pub fn description(self) -> &'static str {
55 match self {
56 Self::Workspace => "workspace",
57 Self::Config => "configuration",
58 Self::Cache => "cache",
59 Self::Telemetry => "telemetry",
60 }
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use std::path::PathBuf;
68
69 struct StaticPaths {
70 root: PathBuf,
71 config: PathBuf,
72 }
73
74 impl WorkspacePaths for StaticPaths {
75 fn workspace_root(&self) -> &Path {
76 &self.root
77 }
78
79 fn config_dir(&self) -> PathBuf {
80 self.config.clone()
81 }
82
83 fn cache_dir(&self) -> Option<PathBuf> {
84 Some(self.root.join("cache"))
85 }
86 }
87
88 #[test]
89 fn resolves_relative_paths() {
90 let paths = StaticPaths {
91 root: PathBuf::from("/tmp/project"),
92 config: PathBuf::from("/tmp/project/config"),
93 };
94
95 assert_eq!(
96 PathResolver::resolve(&paths, "subdir/file.txt"),
97 PathBuf::from("/tmp/project/subdir/file.txt")
98 );
99 assert_eq!(
100 PathResolver::resolve_config(&paths, "settings.toml"),
101 PathBuf::from("/tmp/project/config/settings.toml")
102 );
103 assert_eq!(paths.cache_dir(), Some(PathBuf::from("/tmp/project/cache")));
104 }
105}