pg_embedded_setup_unpriv/cache/
config.rs1use std::path::PathBuf;
7
8use camino::Utf8PathBuf;
9
10const CACHE_SUBDIR: &str = "pg-embedded/binaries";
12
13#[derive(Debug, Clone)]
15pub struct BinaryCacheConfig {
16 pub cache_dir: Utf8PathBuf,
18}
19
20impl BinaryCacheConfig {
21 #[must_use]
23 pub fn new() -> Self {
24 Self {
25 cache_dir: resolve_cache_dir(),
26 }
27 }
28
29 #[must_use]
31 pub const fn with_dir(cache_dir: Utf8PathBuf) -> Self { Self { cache_dir } }
32}
33
34impl Default for BinaryCacheConfig {
35 fn default() -> Self { Self::new() }
36}
37
38#[must_use]
56pub fn resolve_cache_dir() -> Utf8PathBuf {
57 if let Some(dir) = resolve_from_env() {
59 return dir;
60 }
61
62 if let Some(dir) = resolve_from_xdg_cache() {
64 return dir;
65 }
66
67 if let Some(dir) = resolve_from_home() {
69 return dir;
70 }
71
72 let temp_path = std::env::temp_dir().join("pg-embedded").join("binaries");
74 Utf8PathBuf::from_path_buf(temp_path).unwrap_or_else(|path| {
75 Utf8PathBuf::from(path.to_string_lossy().into_owned())
77 })
78}
79
80fn resolve_from_env() -> Option<Utf8PathBuf> {
82 let raw = std::env::var("PG_BINARY_CACHE_DIR").ok()?;
83 let trimmed = raw.trim();
84 if trimmed.is_empty() {
85 return None;
86 }
87 Utf8PathBuf::from_path_buf(PathBuf::from(trimmed)).ok()
88}
89
90fn resolve_from_xdg_cache() -> Option<Utf8PathBuf> {
92 let raw = std::env::var("XDG_CACHE_HOME").ok()?;
93 let trimmed = raw.trim();
94 if trimmed.is_empty() {
95 return None;
96 }
97 let path = Utf8PathBuf::from_path_buf(PathBuf::from(trimmed)).ok()?;
98 Some(path.join(CACHE_SUBDIR))
99}
100
101fn resolve_from_home() -> Option<Utf8PathBuf> {
103 let home = dirs::home_dir()?;
104 let path = Utf8PathBuf::from_path_buf(home).ok()?;
105 Some(path.join(".cache").join(CACHE_SUBDIR))
106}
107
108#[cfg(test)]
109mod tests {
110 use std::ffi::OsString;
112
113 use rstest::rstest;
114
115 use super::*;
116 use crate::test_support::scoped_env;
117
118 #[rstest]
120 #[case::explicit_env_var(Some("/custom/cache/path"), None, platform_path("/custom/cache/path"))]
121 #[case::xdg_fallback(
122 None,
123 Some("/home/testuser/.cache"),
124 platform_cache_path("/home/testuser/.cache")
125 )]
126 #[case::empty_env_var_uses_xdg(
127 Some(""),
128 Some("/home/testuser/.cache"),
129 platform_cache_path("/home/testuser/.cache")
130 )]
131 #[case::whitespace_only_uses_xdg(
132 Some(" "),
133 Some("/home/testuser/.cache"),
134 platform_cache_path("/home/testuser/.cache")
135 )]
136 fn resolve_cache_dir_respects_env_priority(
137 #[case] pg_cache_dir: Option<&str>,
138 #[case] xdg_cache_home: Option<&str>,
139 #[case] expected: Utf8PathBuf,
140 ) {
141 let env_vars = vec![
142 (
143 OsString::from("PG_BINARY_CACHE_DIR"),
144 pg_cache_dir.map(OsString::from),
145 ),
146 (
147 OsString::from("XDG_CACHE_HOME"),
148 xdg_cache_home.map(OsString::from),
149 ),
150 ];
151
152 let _guard = scoped_env(env_vars);
153 let result = resolve_cache_dir();
154 assert_eq!(result, expected);
155 }
156
157 fn platform_path(path: &str) -> Utf8PathBuf { Utf8PathBuf::from(path) }
158
159 fn platform_cache_path(cache_home: &str) -> Utf8PathBuf {
160 platform_path(cache_home).join(CACHE_SUBDIR)
161 }
162
163 #[test]
164 fn binary_cache_config_default_uses_resolved_dir() {
165 let _guard = scoped_env([
166 (OsString::from("PG_BINARY_CACHE_DIR"), None),
167 (OsString::from("XDG_CACHE_HOME"), None),
168 ]);
169 let config = BinaryCacheConfig::default();
170 assert!(config.cache_dir.as_str().contains("pg-embedded"));
171 }
172
173 #[test]
174 fn binary_cache_config_with_dir_uses_provided_path() {
175 let custom_path = Utf8PathBuf::from("/custom/path");
176 let config = BinaryCacheConfig::with_dir(custom_path.clone());
177 assert_eq!(config.cache_dir, custom_path);
178 }
179}