use std::path::PathBuf;
pub const DEFAULT_MAX_HISTORY_ENTRIES: usize = 10_000;
pub const DEFAULT_MAX_INDEX_BYTES: u64 = 10 * 1024 * 1024;
pub const ENV_CONFIG_DIR: &str = "SQRY_CONFIG_DIR";
pub const ENV_NO_HISTORY: &str = "SQRY_NO_HISTORY";
pub const ENV_NO_REDACT: &str = "SQRY_NO_REDACT";
#[derive(Debug, Clone)]
pub struct PersistenceConfig {
pub global_dir_override: Option<PathBuf>,
pub local_dir_override: Option<PathBuf>,
pub history_enabled: bool,
pub max_history_entries: usize,
pub max_index_bytes: u64,
pub redact_secrets: bool,
}
impl Default for PersistenceConfig {
fn default() -> Self {
Self {
global_dir_override: None,
local_dir_override: None,
history_enabled: true,
max_history_entries: DEFAULT_MAX_HISTORY_ENTRIES,
max_index_bytes: DEFAULT_MAX_INDEX_BYTES,
redact_secrets: true,
}
}
}
impl PersistenceConfig {
#[must_use]
pub fn from_env() -> Self {
Self::from_env_and_cli(None, None, false)
}
#[must_use]
pub fn from_env_and_cli(
cli_config_dir: Option<PathBuf>,
cli_max_history: Option<usize>,
cli_no_history: bool,
) -> Self {
let global_dir_override =
cli_config_dir.or_else(|| std::env::var(ENV_CONFIG_DIR).ok().map(PathBuf::from));
let history_enabled = if cli_no_history {
false
} else {
std::env::var(ENV_NO_HISTORY)
.map(|v| !["1", "true", "yes"].contains(&v.to_lowercase().as_str()))
.unwrap_or(true)
};
let redact_secrets = std::env::var(ENV_NO_REDACT)
.map(|v| !["1", "true", "yes"].contains(&v.to_lowercase().as_str()))
.unwrap_or(true);
Self {
global_dir_override,
local_dir_override: None,
history_enabled,
max_history_entries: cli_max_history.unwrap_or(DEFAULT_MAX_HISTORY_ENTRIES),
max_index_bytes: DEFAULT_MAX_INDEX_BYTES,
redact_secrets,
}
}
pub fn global_config_dir(&self) -> anyhow::Result<PathBuf> {
if let Some(ref override_path) = self.global_dir_override {
return Ok(override_path.clone());
}
dirs::config_dir()
.map(|p| p.join("sqry"))
.ok_or_else(|| anyhow::anyhow!("Could not determine config directory"))
}
#[must_use]
pub fn local_config_dir(&self, project_root: &std::path::Path) -> PathBuf {
self.local_dir_override
.clone()
.unwrap_or_else(|| project_root.to_path_buf())
}
}
pub fn global_config_dir() -> anyhow::Result<PathBuf> {
dirs::config_dir()
.map(|p| p.join("sqry"))
.ok_or_else(|| anyhow::anyhow!("Could not determine config directory"))
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
#[test]
fn test_default_config() {
let config = PersistenceConfig::default();
assert!(config.global_dir_override.is_none());
assert!(config.local_dir_override.is_none());
assert!(config.history_enabled);
assert_eq!(config.max_history_entries, DEFAULT_MAX_HISTORY_ENTRIES);
assert_eq!(config.max_index_bytes, DEFAULT_MAX_INDEX_BYTES);
assert!(
config.redact_secrets,
"redact_secrets should default to true"
);
}
#[test]
#[serial]
fn test_cli_config_dir_takes_precedence() {
unsafe {
std::env::set_var(ENV_CONFIG_DIR, "/env/path");
}
let config =
PersistenceConfig::from_env_and_cli(Some(PathBuf::from("/cli/path")), None, false);
assert_eq!(config.global_dir_override, Some(PathBuf::from("/cli/path")));
unsafe {
std::env::remove_var(ENV_CONFIG_DIR);
}
}
#[test]
#[serial]
fn test_env_config_dir_fallback() {
unsafe {
std::env::set_var(ENV_CONFIG_DIR, "/env/path");
}
let config = PersistenceConfig::from_env_and_cli(None, None, false);
assert_eq!(config.global_dir_override, Some(PathBuf::from("/env/path")));
unsafe {
std::env::remove_var(ENV_CONFIG_DIR);
}
}
#[test]
fn test_cli_no_history_disables_history() {
let config = PersistenceConfig::from_env_and_cli(None, None, true);
assert!(!config.history_enabled);
}
#[test]
#[serial]
fn test_env_no_history_disables_history() {
unsafe {
std::env::set_var(ENV_NO_HISTORY, "1");
}
let config = PersistenceConfig::from_env_and_cli(None, None, false);
assert!(!config.history_enabled);
unsafe {
std::env::remove_var(ENV_NO_HISTORY);
}
}
#[test]
#[serial]
fn test_redact_secrets_default_enabled() {
let config = PersistenceConfig::from_env_and_cli(None, None, false);
assert!(
config.redact_secrets,
"redact_secrets should default to true"
);
}
#[test]
#[serial]
fn test_redact_secrets_disabled_via_env() {
unsafe {
std::env::set_var(ENV_NO_REDACT, "1");
}
let config = PersistenceConfig::from_env_and_cli(None, None, false);
assert!(
!config.redact_secrets,
"SQRY_NO_REDACT=1 should disable redaction"
);
unsafe {
std::env::remove_var(ENV_NO_REDACT);
}
}
#[test]
fn test_global_config_dir_with_override() {
let config = PersistenceConfig {
global_dir_override: Some(PathBuf::from("/custom/path")),
..Default::default()
};
let dir = config.global_config_dir().expect("should succeed");
assert_eq!(dir, PathBuf::from("/custom/path"));
}
#[test]
fn test_local_config_dir() {
let config = PersistenceConfig::default();
let project = PathBuf::from("/home/user/project");
let local_dir = config.local_config_dir(&project);
assert_eq!(local_dir, project);
}
#[test]
fn test_local_config_dir_with_override() {
let config = PersistenceConfig {
local_dir_override: Some(PathBuf::from("/custom/local")),
..Default::default()
};
let project = PathBuf::from("/home/user/project");
let local_dir = config.local_config_dir(&project);
assert_eq!(local_dir, PathBuf::from("/custom/local"));
}
}