use std::path::PathBuf;
use crate::env::Environment;
const CONTAINER_BASE_PATH: &str = "/app";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RuntimePaths {
pub config_dir: PathBuf,
pub secrets_dir: PathBuf,
pub data_dir: PathBuf,
pub temp_dir: PathBuf,
pub logs_dir: PathBuf,
pub cache_dir: PathBuf,
pub run_dir: PathBuf,
}
impl RuntimePaths {
#[must_use]
pub fn discover() -> Self {
Self::discover_for(Environment::detect())
}
#[must_use]
pub fn discover_for(env: Environment) -> Self {
match env {
Environment::Kubernetes | Environment::Docker | Environment::Container => {
Self::container_paths()
}
Environment::BareMetal => Self::local_paths(),
}
}
fn container_paths() -> Self {
let base = std::env::var("CONTAINER_BASE_PATH")
.unwrap_or_else(|_| CONTAINER_BASE_PATH.to_string());
let base_path = PathBuf::from(&base);
Self {
config_dir: base_path.join("config"),
secrets_dir: base_path.join("secrets"),
data_dir: base_path.join("data"),
temp_dir: base_path.join("tmp"),
logs_dir: base_path.join("logs"),
cache_dir: base_path.join("cache"),
run_dir: base_path.join("run"),
}
}
fn local_paths() -> Self {
let app_name = std::env::var("APP_NAME").unwrap_or_else(|_| "hs-app".to_string());
let config_dir = dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("~/.config"))
.join(&app_name);
let data_dir = dirs::data_dir()
.unwrap_or_else(|| PathBuf::from("~/.local/share"))
.join(&app_name);
let cache_dir = dirs::cache_dir()
.unwrap_or_else(|| PathBuf::from("~/.cache"))
.join(&app_name);
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("~"));
Self {
config_dir,
secrets_dir: home_dir.join(format!(".{app_name}")).join("secrets"),
data_dir: data_dir.clone(),
temp_dir: std::env::temp_dir().join(&app_name),
logs_dir: data_dir.join("logs"),
cache_dir,
run_dir: dirs::runtime_dir()
.unwrap_or_else(|| PathBuf::from("/tmp"))
.join(&app_name),
}
}
pub fn ensure_dirs(&self) -> std::io::Result<()> {
std::fs::create_dir_all(&self.config_dir)?;
std::fs::create_dir_all(&self.secrets_dir)?;
std::fs::create_dir_all(&self.data_dir)?;
std::fs::create_dir_all(&self.temp_dir)?;
std::fs::create_dir_all(&self.logs_dir)?;
std::fs::create_dir_all(&self.cache_dir)?;
std::fs::create_dir_all(&self.run_dir)?;
Ok(())
}
#[must_use]
pub fn all_exist(&self) -> bool {
self.config_dir.exists()
&& self.secrets_dir.exists()
&& self.data_dir.exists()
&& self.temp_dir.exists()
&& self.logs_dir.exists()
&& self.cache_dir.exists()
&& self.run_dir.exists()
}
}
impl Default for RuntimePaths {
fn default() -> Self {
Self::discover()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_container_paths() {
let paths = RuntimePaths::discover_for(Environment::Kubernetes);
assert_eq!(paths.config_dir, PathBuf::from("/app/config"));
assert_eq!(paths.secrets_dir, PathBuf::from("/app/secrets"));
assert_eq!(paths.data_dir, PathBuf::from("/app/data"));
assert_eq!(paths.temp_dir, PathBuf::from("/app/tmp"));
assert_eq!(paths.logs_dir, PathBuf::from("/app/logs"));
assert_eq!(paths.cache_dir, PathBuf::from("/app/cache"));
assert_eq!(paths.run_dir, PathBuf::from("/app/run"));
}
#[test]
fn test_docker_uses_container_paths() {
let docker_paths = RuntimePaths::discover_for(Environment::Docker);
let k8s_paths = RuntimePaths::discover_for(Environment::Kubernetes);
assert_eq!(docker_paths, k8s_paths);
}
#[test]
fn test_local_paths_use_xdg() {
let paths = RuntimePaths::discover_for(Environment::BareMetal);
assert!(!paths.config_dir.starts_with("/app"));
assert!(!paths.data_dir.starts_with("/app"));
}
#[test]
fn test_custom_container_base_path() {
temp_env::with_var("CONTAINER_BASE_PATH", Some("/custom"), || {
let paths = RuntimePaths::discover_for(Environment::Docker);
assert_eq!(paths.config_dir, PathBuf::from("/custom/config"));
});
}
}