use std::fmt;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
const DEFAULT_RETENTION_DAYS: u64 = 14;
const DEFAULT_INCREMENTAL_RETENTION_HOURS: u64 = 24;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
pub retention_days: u64,
pub incremental_retention_hours: u64,
pub max_reclaim_bytes: Option<u64>,
pub crate_path: Option<PathBuf>,
}
impl Default for Config {
fn default() -> Self {
Config {
retention_days: DEFAULT_RETENTION_DAYS,
incremental_retention_hours: DEFAULT_INCREMENTAL_RETENTION_HOURS,
max_reclaim_bytes: None,
crate_path: None,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ConfigError {
Read { path: PathBuf, message: String },
Parse { path: PathBuf, message: String },
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConfigError::Read { path, message } => {
write!(f, "failed to read {}: {message}", path.display())
}
ConfigError::Parse { path, message } => {
write!(f, "failed to parse {}: {message}", path.display())
}
}
}
}
impl std::error::Error for ConfigError {}
pub fn load(root: &Path) -> Result<Config, ConfigError> {
load_file(&root.join("target-gc.toml"))
}
pub fn load_file(path: &Path) -> Result<Config, ConfigError> {
if !path.exists() {
return Ok(Config::default());
}
let text = std::fs::read_to_string(path).map_err(|e| ConfigError::Read {
path: path.to_path_buf(),
message: e.to_string(),
})?;
parse(&text).map_err(|message| ConfigError::Parse {
path: path.to_path_buf(),
message,
})
}
fn parse(text: &str) -> Result<Config, String> {
toml::from_str(text).map_err(|e| e.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn missing_file_yields_default() {
let dir =
std::env::temp_dir().join(format!("target-gc-cfg-missing-{}", std::process::id()));
let config = load(&dir).expect("missing file is ok");
assert_eq!(config, Config::default());
}
#[test]
fn default_retention_is_fourteen_days() {
assert_eq!(Config::default().retention_days, 14);
}
#[test]
fn valid_file_parses_retention() {
let config = parse(
"retention_days = 30\nincremental_retention_hours = 6\nmax_reclaim_bytes = 1024\n",
)
.expect("parse");
assert_eq!(config.retention_days, 30);
assert_eq!(config.incremental_retention_hours, 6);
assert_eq!(config.max_reclaim_bytes, Some(1024));
assert_eq!(config.crate_path, None);
}
#[test]
fn crate_path_parses() {
let config = parse("crate_path = \"crates/core\"\n").expect("parse");
assert_eq!(config.crate_path, Some(PathBuf::from("crates/core")));
assert_eq!(config.retention_days, 14);
assert_eq!(config.incremental_retention_hours, 24);
}
#[test]
fn invalid_toml_returns_err() {
let result = parse("retention_days = [[[broken");
assert!(result.is_err());
}
}