use std::path::PathBuf;
use camino::Utf8PathBuf;
const CACHE_SUBDIR: &str = "pg-embedded/binaries";
#[derive(Debug, Clone)]
pub struct BinaryCacheConfig {
pub cache_dir: Utf8PathBuf,
}
impl BinaryCacheConfig {
#[must_use]
pub fn new() -> Self {
Self {
cache_dir: resolve_cache_dir(),
}
}
#[must_use]
pub const fn with_dir(cache_dir: Utf8PathBuf) -> Self { Self { cache_dir } }
}
impl Default for BinaryCacheConfig {
fn default() -> Self { Self::new() }
}
#[must_use]
pub fn resolve_cache_dir() -> Utf8PathBuf {
if let Some(dir) = resolve_from_env() {
return dir;
}
if let Some(dir) = resolve_from_xdg_cache() {
return dir;
}
if let Some(dir) = resolve_from_home() {
return dir;
}
let temp_path = std::env::temp_dir().join("pg-embedded").join("binaries");
Utf8PathBuf::from_path_buf(temp_path).unwrap_or_else(|path| {
Utf8PathBuf::from(path.to_string_lossy().into_owned())
})
}
fn resolve_from_env() -> Option<Utf8PathBuf> {
let raw = std::env::var("PG_BINARY_CACHE_DIR").ok()?;
let trimmed = raw.trim();
if trimmed.is_empty() {
return None;
}
Utf8PathBuf::from_path_buf(PathBuf::from(trimmed)).ok()
}
fn resolve_from_xdg_cache() -> Option<Utf8PathBuf> {
let raw = std::env::var("XDG_CACHE_HOME").ok()?;
let trimmed = raw.trim();
if trimmed.is_empty() {
return None;
}
let path = Utf8PathBuf::from_path_buf(PathBuf::from(trimmed)).ok()?;
Some(path.join(CACHE_SUBDIR))
}
fn resolve_from_home() -> Option<Utf8PathBuf> {
let home = dirs::home_dir()?;
let path = Utf8PathBuf::from_path_buf(home).ok()?;
Some(path.join(".cache").join(CACHE_SUBDIR))
}
#[cfg(test)]
mod tests {
use std::ffi::OsString;
use rstest::rstest;
use super::*;
use crate::test_support::scoped_env;
#[rstest]
#[case::explicit_env_var(Some("/custom/cache/path"), None, platform_path("/custom/cache/path"))]
#[case::xdg_fallback(
None,
Some("/home/testuser/.cache"),
platform_cache_path("/home/testuser/.cache")
)]
#[case::empty_env_var_uses_xdg(
Some(""),
Some("/home/testuser/.cache"),
platform_cache_path("/home/testuser/.cache")
)]
#[case::whitespace_only_uses_xdg(
Some(" "),
Some("/home/testuser/.cache"),
platform_cache_path("/home/testuser/.cache")
)]
fn resolve_cache_dir_respects_env_priority(
#[case] pg_cache_dir: Option<&str>,
#[case] xdg_cache_home: Option<&str>,
#[case] expected: Utf8PathBuf,
) {
let env_vars = vec![
(
OsString::from("PG_BINARY_CACHE_DIR"),
pg_cache_dir.map(OsString::from),
),
(
OsString::from("XDG_CACHE_HOME"),
xdg_cache_home.map(OsString::from),
),
];
let _guard = scoped_env(env_vars);
let result = resolve_cache_dir();
assert_eq!(result, expected);
}
fn platform_path(path: &str) -> Utf8PathBuf { Utf8PathBuf::from(path) }
fn platform_cache_path(cache_home: &str) -> Utf8PathBuf {
platform_path(cache_home).join(CACHE_SUBDIR)
}
#[test]
fn binary_cache_config_default_uses_resolved_dir() {
let _guard = scoped_env([
(OsString::from("PG_BINARY_CACHE_DIR"), None),
(OsString::from("XDG_CACHE_HOME"), None),
]);
let config = BinaryCacheConfig::default();
assert!(config.cache_dir.as_str().contains("pg-embedded"));
}
#[test]
fn binary_cache_config_with_dir_uses_provided_path() {
let custom_path = Utf8PathBuf::from("/custom/path");
let config = BinaryCacheConfig::with_dir(custom_path.clone());
assert_eq!(config.cache_dir, custom_path);
}
}