use std::fs;
use std::path::Path;
use openmoat_hosts::{Host, RelaxedKey};
use serde_json::{Map, Value};
use super::digest::{COSMETIC_KEY, is_claude_settings};
use super::sha256_hex;
pub(super) fn listed(path: &Path) -> Option<&'static [RelaxedKey]> {
Host::ALL
.into_iter()
.filter_map(|host| host.spec().install.as_ref())
.filter(|install| !install.relaxed_keys.is_empty())
.find(|install| path.ends_with(Path::new(install.dir_name).join(install.config_file)))
.map(|install| install.relaxed_keys)
}
pub(super) fn pinned_rest(path: &Path) -> Option<String> {
let keys = listed(path)?;
if !fs::symlink_metadata(path).ok()?.file_type().is_file() {
return None;
}
rest_digest(path, &fs::read(path).ok()?, keys)
}
pub(super) fn only_relaxed(path: &Path, bytes: &[u8], keys: &[RelaxedKey], pinned: &str) -> bool {
rest_digest(path, bytes, keys).is_some_and(|rest| rest == pinned)
}
fn rest_digest(path: &Path, bytes: &[u8], keys: &[RelaxedKey]) -> Option<String> {
let Value::Object(mut root) = serde_json::from_slice(bytes).ok()? else {
return None;
};
if is_claude_settings(path) {
root.remove(COSMETIC_KEY);
}
for key in keys {
let names: Vec<&str> = key.path.split('.').collect();
take_out(&mut root, &names, key);
}
serde_json::to_vec(&Value::Object(root))
.ok()
.map(|b| sha256_hex(&b))
}
fn take_out(object: &mut Map<String, Value>, names: &[&str], key: &RelaxedKey) {
let Some((first, rest)) = names.split_first() else {
return;
};
if rest.is_empty() {
if object.get(*first).is_some_and(|v| key.shape.holds(v)) {
object.remove(*first);
}
return;
}
let Some(Value::Object(inner)) = object.get_mut(*first) else {
return;
};
take_out(inner, rest, key);
if inner.is_empty() {
object.remove(*first);
}
}
#[cfg(test)]
mod tests;