config/src/fingerprint.rs
67 lines
use codex_app_server_protocol::ConfigLayerMetadata;use serde_json::Value as JsonValue;use sha2::Digest;use sha2::Sha256;use std::collections::HashMap;use toml::Value as TomlValue;pub(super) fn record_origins( value: &TomlValue, meta: &ConfigLayerMetadata, path: &mut Vec<String>, origins: &mut HashMap<String, ConfigLayerMetadata>,) { match value { TomlValue::Table(table) => { for (key, val) in table { path.push(key.clone()); record_origins(val, meta, path, origins); path.pop(); } } TomlValue::Array(items) => { for (idx, item) in (0_i32..).zip(items.iter()) { path.push(idx.to_string()); record_origins(item, meta, path, origins); path.pop(); } } _ => { if !path.is_empty() { origins.insert(path.join("."), meta.clone()); } } }}pub fn version_for_toml(value: &TomlValue) -> String { let json = serde_json::to_value(value).unwrap_or(JsonValue::Null); let canonical = canonical_json(&json); let serialized = serde_json::to_vec(&canonical).unwrap_or_default(); let mut hasher = Sha256::new(); hasher.update(serialized); let hash = hasher.finalize(); let hex = hash .iter() .map(|byte| format!("{byte:02x}")) .collect::<String>(); format!("sha256:{hex}")}fn canonical_json(value: &JsonValue) -> JsonValue { match value { JsonValue::Object(map) => { let mut sorted = serde_json::Map::new(); let mut keys = map.keys().cloned().collect::<Vec<_>>(); keys.sort(); for key in keys { if let Some(val) = map.get(&key) { sorted.insert(key, canonical_json(val)); } } JsonValue::Object(sorted) } JsonValue::Array(items) => JsonValue::Array(items.iter().map(canonical_json).collect()), other => other.clone(), }}