hyperi_rustlib/
runtime.rs1use std::path::PathBuf;
15
16use crate::env::Environment;
17
18const CONTAINER_BASE_PATH: &str = "/app";
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct RuntimePaths {
24 pub config_dir: PathBuf,
26 pub secrets_dir: PathBuf,
28 pub data_dir: PathBuf,
30 pub temp_dir: PathBuf,
32 pub logs_dir: PathBuf,
34 pub cache_dir: PathBuf,
36 pub run_dir: PathBuf,
38}
39
40impl RuntimePaths {
41 #[must_use]
43 pub fn discover() -> Self {
44 Self::discover_for(Environment::detect())
45 }
46
47 #[must_use]
49 pub fn discover_for(env: Environment) -> Self {
50 match env {
51 Environment::Kubernetes | Environment::Docker | Environment::Container => {
52 Self::container_paths()
53 }
54 Environment::BareMetal => Self::local_paths(),
55 }
56 }
57
58 fn container_paths() -> Self {
60 let base = std::env::var("CONTAINER_BASE_PATH")
61 .unwrap_or_else(|_| CONTAINER_BASE_PATH.to_string());
62 let base_path = PathBuf::from(&base);
63
64 Self {
65 config_dir: base_path.join("config"),
66 secrets_dir: base_path.join("secrets"),
67 data_dir: base_path.join("data"),
68 temp_dir: base_path.join("tmp"),
69 logs_dir: base_path.join("logs"),
70 cache_dir: base_path.join("cache"),
71 run_dir: base_path.join("run"),
72 }
73 }
74
75 fn local_paths() -> Self {
77 let app_name = std::env::var("APP_NAME").unwrap_or_else(|_| "hs-app".to_string());
78
79 let config_dir = dirs::config_dir()
81 .unwrap_or_else(|| PathBuf::from("~/.config"))
82 .join(&app_name);
83
84 let data_dir = dirs::data_dir()
85 .unwrap_or_else(|| PathBuf::from("~/.local/share"))
86 .join(&app_name);
87
88 let cache_dir = dirs::cache_dir()
89 .unwrap_or_else(|| PathBuf::from("~/.cache"))
90 .join(&app_name);
91
92 let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("~"));
93
94 Self {
95 config_dir,
96 secrets_dir: home_dir.join(format!(".{app_name}")).join("secrets"),
97 data_dir: data_dir.clone(),
98 temp_dir: std::env::temp_dir().join(&app_name),
99 logs_dir: data_dir.join("logs"),
100 cache_dir,
101 run_dir: dirs::runtime_dir()
102 .unwrap_or_else(|| PathBuf::from("/tmp"))
103 .join(&app_name),
104 }
105 }
106
107 pub fn ensure_dirs(&self) -> std::io::Result<()> {
113 std::fs::create_dir_all(&self.config_dir)?;
114 std::fs::create_dir_all(&self.secrets_dir)?;
115 std::fs::create_dir_all(&self.data_dir)?;
116 std::fs::create_dir_all(&self.temp_dir)?;
117 std::fs::create_dir_all(&self.logs_dir)?;
118 std::fs::create_dir_all(&self.cache_dir)?;
119 std::fs::create_dir_all(&self.run_dir)?;
120 Ok(())
121 }
122
123 #[must_use]
125 pub fn all_exist(&self) -> bool {
126 self.config_dir.exists()
127 && self.secrets_dir.exists()
128 && self.data_dir.exists()
129 && self.temp_dir.exists()
130 && self.logs_dir.exists()
131 && self.cache_dir.exists()
132 && self.run_dir.exists()
133 }
134}
135
136impl Default for RuntimePaths {
137 fn default() -> Self {
138 Self::discover()
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_container_paths() {
148 let paths = RuntimePaths::discover_for(Environment::Kubernetes);
149
150 assert_eq!(paths.config_dir, PathBuf::from("/app/config"));
151 assert_eq!(paths.secrets_dir, PathBuf::from("/app/secrets"));
152 assert_eq!(paths.data_dir, PathBuf::from("/app/data"));
153 assert_eq!(paths.temp_dir, PathBuf::from("/app/tmp"));
154 assert_eq!(paths.logs_dir, PathBuf::from("/app/logs"));
155 assert_eq!(paths.cache_dir, PathBuf::from("/app/cache"));
156 assert_eq!(paths.run_dir, PathBuf::from("/app/run"));
157 }
158
159 #[test]
160 fn test_docker_uses_container_paths() {
161 let docker_paths = RuntimePaths::discover_for(Environment::Docker);
162 let k8s_paths = RuntimePaths::discover_for(Environment::Kubernetes);
163
164 assert_eq!(docker_paths, k8s_paths);
166 }
167
168 #[test]
169 fn test_local_paths_use_xdg() {
170 let paths = RuntimePaths::discover_for(Environment::BareMetal);
171
172 assert!(!paths.config_dir.starts_with("/app"));
174 assert!(!paths.data_dir.starts_with("/app"));
175 }
176
177 #[test]
178 fn test_custom_container_base_path() {
179 temp_env::with_var("CONTAINER_BASE_PATH", Some("/custom"), || {
180 let paths = RuntimePaths::discover_for(Environment::Docker);
181 assert_eq!(paths.config_dir, PathBuf::from("/custom/config"));
182 });
183 }
184}