use std::path::Path;
use crate::config::Config;
use crate::config::opencrabs_home;
use crate::config::types::KeysFile;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProtectedFile {
Config,
Keys,
}
impl ProtectedFile {
pub fn file_name(self) -> &'static str {
match self {
ProtectedFile::Config => "config.toml",
ProtectedFile::Keys => "keys.toml",
}
}
}
fn normalize(path: &Path) -> std::path::PathBuf {
path.canonicalize().unwrap_or_else(|_| path.to_path_buf())
}
pub fn protected_file(path: &Path) -> Option<ProtectedFile> {
let home = opencrabs_home();
let cfg = Config::system_config_path().unwrap_or_else(|| home.join("config.toml"));
let keys = home.join("keys.toml");
let target = normalize(path);
if target == normalize(&cfg) {
Some(ProtectedFile::Config)
} else if target == normalize(&keys) {
Some(ProtectedFile::Keys)
} else {
None
}
}
pub fn parses(kind: ProtectedFile, content: &str) -> Result<(), String> {
let res = match kind {
ProtectedFile::Config => toml::from_str::<Config>(content).map(|_| ()),
ProtectedFile::Keys => toml::from_str::<KeysFile>(content).map(|_| ()),
};
res.map_err(|e| e.to_string())
}
pub fn deny_if_would_break(path: &Path, content: &str) -> Result<(), String> {
let Some(kind) = protected_file(path) else {
return Ok(());
};
if let Err(e) = parses(kind, content) {
let name = kind.file_name();
tracing::error!(
target: "config_guard",
"Denied write to {name}: the new content does not parse ({e}) — file left unchanged"
);
return Err(format!(
"Refused to write {name}: the new content is not valid ({name} must stay parseable) — \
{e}. The file was NOT changed. Fix the TOML, or use the config_manager tool for config \
edits instead of editing the file directly."
));
}
Ok(())
}