pg_embedded_setup_unpriv/cache/
config.rs1use camino::Utf8PathBuf;
7use std::path::PathBuf;
8
9const CACHE_SUBDIR: &str = "pg-embedded/binaries";
11
12#[derive(Debug, Clone)]
14pub struct BinaryCacheConfig {
15 pub cache_dir: Utf8PathBuf,
17}
18
19impl BinaryCacheConfig {
20 #[must_use]
22 pub fn new() -> Self {
23 Self {
24 cache_dir: resolve_cache_dir(),
25 }
26 }
27
28 #[must_use]
30 pub const fn with_dir(cache_dir: Utf8PathBuf) -> Self {
31 Self { cache_dir }
32 }
33}
34
35impl Default for BinaryCacheConfig {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41#[must_use]
59pub fn resolve_cache_dir() -> Utf8PathBuf {
60 if let Some(dir) = resolve_from_env() {
62 return dir;
63 }
64
65 if let Some(dir) = resolve_from_xdg_cache() {
67 return dir;
68 }
69
70 if let Some(dir) = resolve_from_home() {
72 return dir;
73 }
74
75 let temp_path = std::env::temp_dir().join("pg-embedded").join("binaries");
77 Utf8PathBuf::from_path_buf(temp_path).unwrap_or_else(|path| {
78 Utf8PathBuf::from(path.to_string_lossy().into_owned())
80 })
81}
82
83fn resolve_from_env() -> Option<Utf8PathBuf> {
85 let raw = std::env::var("PG_BINARY_CACHE_DIR").ok()?;
86 let trimmed = raw.trim();
87 if trimmed.is_empty() {
88 return None;
89 }
90 Utf8PathBuf::from_path_buf(PathBuf::from(trimmed)).ok()
91}
92
93fn resolve_from_xdg_cache() -> Option<Utf8PathBuf> {
95 let raw = std::env::var("XDG_CACHE_HOME").ok()?;
96 let trimmed = raw.trim();
97 if trimmed.is_empty() {
98 return None;
99 }
100 let path = Utf8PathBuf::from_path_buf(PathBuf::from(trimmed)).ok()?;
101 Some(path.join(CACHE_SUBDIR))
102}
103
104fn resolve_from_home() -> Option<Utf8PathBuf> {
106 let home = dirs::home_dir()?;
107 let path = Utf8PathBuf::from_path_buf(home).ok()?;
108 Some(path.join(".cache").join(CACHE_SUBDIR))
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::test_support::scoped_env;
115 use rstest::rstest;
116 use std::ffi::OsString;
117
118 #[rstest]
120 #[case::explicit_env_var(Some("/custom/cache/path"), None, "/custom/cache/path")]
121 #[case::xdg_fallback(
122 None,
123 Some("/home/testuser/.cache"),
124 &format!("/home/testuser/.cache/{CACHE_SUBDIR}")
125 )]
126 #[case::empty_env_var_uses_xdg(
127 Some(""),
128 Some("/home/testuser/.cache"),
129 &format!("/home/testuser/.cache/{CACHE_SUBDIR}")
130 )]
131 #[case::whitespace_only_uses_xdg(
132 Some(" "),
133 Some("/home/testuser/.cache"),
134 &format!("/home/testuser/.cache/{CACHE_SUBDIR}")
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: &str,
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.as_str(), expected);
155 }
156
157 #[test]
158 fn binary_cache_config_default_uses_resolved_dir() {
159 let _guard = scoped_env([
160 (OsString::from("PG_BINARY_CACHE_DIR"), None),
161 (OsString::from("XDG_CACHE_HOME"), None),
162 ]);
163 let config = BinaryCacheConfig::default();
164 assert!(config.cache_dir.as_str().contains("pg-embedded"));
165 }
166
167 #[test]
168 fn binary_cache_config_with_dir_uses_provided_path() {
169 let custom_path = Utf8PathBuf::from("/custom/path");
170 let config = BinaryCacheConfig::with_dir(custom_path.clone());
171 assert_eq!(config.cache_dir, custom_path);
172 }
173}