Skip to main content

telar_services_core/
paths.rs

1use std::path::PathBuf;
2
3pub trait AppPathsProvider: Send + Sync {
4    fn config_dir(&self) -> Option<PathBuf>;
5    fn data_dir(&self) -> Option<PathBuf>;
6    fn cache_dir(&self) -> Option<PathBuf>;
7
8    /// Machine-written state the user never edits. Defaults to [`data_dir`](Self::data_dir), which is where it
9    /// lives on every platform that does not separate the two.
10    fn state_dir(&self) -> Option<PathBuf> {
11        self.data_dir()
12    }
13    /// Session-scoped runtime files — a socket, a lock. `None` where the platform has no such place, which is
14    /// the caller's cue to pick its own rather than be handed a directory that outlives the session.
15    fn runtime_dir(&self) -> Option<PathBuf> {
16        None
17    }
18
19    /// OS system-font directory to scan for fallback family resolution. `None` on platforms where the
20    /// renderer's default font discovery suffices (desktop); the platform adapter overrides it where the OS
21    /// keeps fonts in a fixed location (Android → `/system/fonts`).
22    fn system_fonts_dir(&self) -> Option<PathBuf> {
23        None
24    }
25    /// Sans-serif family names to try in priority order, OS/OEM-specific. Empty when default discovery
26    /// suffices; the platform adapter overrides it (Android OEM font stacks).
27    fn sans_serif_candidates(&self) -> Vec<String> {
28        Vec::new()
29    }
30}
31
32/// A provider that reports no directories at all, so nothing it is handed to touches a real XDG path.
33///
34/// Public rather than test-only: a preview window, a headless rasterise and every integration test want the
35/// same answer, and each had written its own copy of these three `None`s.
36pub struct NoPaths;
37
38impl AppPathsProvider for NoPaths {
39    fn config_dir(&self) -> Option<PathBuf> {
40        None
41    }
42
43    fn data_dir(&self) -> Option<PathBuf> {
44        None
45    }
46
47    fn cache_dir(&self) -> Option<PathBuf> {
48        None
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    struct MockPathsProvider {
57        config: PathBuf,
58        data: PathBuf,
59        cache: PathBuf,
60    }
61
62    impl AppPathsProvider for MockPathsProvider {
63        fn config_dir(&self) -> Option<PathBuf> {
64            Some(self.config.clone())
65        }
66
67        fn data_dir(&self) -> Option<PathBuf> {
68            Some(self.data.clone())
69        }
70
71        fn cache_dir(&self) -> Option<PathBuf> {
72            Some(self.cache.clone())
73        }
74    }
75
76    #[test]
77    fn test_mock_provider_config_dir() {
78        let provider = MockPathsProvider {
79            config: PathBuf::from("/mock/config"),
80            data: PathBuf::from("/mock/data"),
81            cache: PathBuf::from("/mock/cache"),
82        };
83
84        assert_eq!(provider.config_dir(), Some(PathBuf::from("/mock/config")));
85    }
86
87    #[test]
88    fn test_mock_provider_data_dir() {
89        let provider = MockPathsProvider {
90            config: PathBuf::from("/mock/config"),
91            data: PathBuf::from("/mock/data"),
92            cache: PathBuf::from("/mock/cache"),
93        };
94
95        assert_eq!(provider.data_dir(), Some(PathBuf::from("/mock/data")));
96    }
97
98    #[test]
99    fn test_mock_provider_cache_dir() {
100        let provider = MockPathsProvider {
101            config: PathBuf::from("/mock/config"),
102            data: PathBuf::from("/mock/data"),
103            cache: PathBuf::from("/mock/cache"),
104        };
105
106        assert_eq!(provider.cache_dir(), Some(PathBuf::from("/mock/cache")));
107    }
108
109    #[test]
110    fn test_none_provider_handles_missing_paths() {
111        let provider = NoPaths;
112
113        assert_eq!(provider.config_dir(), None);
114        assert_eq!(provider.data_dir(), None);
115        assert_eq!(provider.cache_dir(), None);
116    }
117}