Simplify configuration fingerprint canonicalization (#49295)

## What changed

Replace the custom recursive JSON canonicalizer with `serde_json::Value::sort_all_objects()` before hashing configuration values, preserving existing fingerprints.

## Testing

Add a regression test that checks reordered keys in nested tables and tables inside arrays produce the same existing fingerprint.

GitOrigin-RevId: 890e79865a1d690adea3b35f19fcc6c30dbd17ce
This commit is contained in:
jif
2026-09-29 16:10:27 +00:00
committed by copyberry
parent d79a95bdf8
commit cf12c86dc5
2 changed files with 32 additions and 20 deletions
+6 -20
View File
@@ -52,9 +52,9 @@ pub(super) fn record_origins(
}
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 json = serde_json::to_value(value).unwrap_or(JsonValue::Null);
json.sort_all_objects();
let serialized = serde_json::to_vec(&json).unwrap_or_default();
let mut hasher = Sha256::new();
hasher.update(serialized);
let hash = hasher.finalize();
@@ -65,20 +65,6 @@ pub fn version_for_toml(value: &TomlValue) -> 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(),
}
}
#[cfg(test)]
#[path = "fingerprint_tests.rs"]
mod tests;
+26
View File
@@ -0,0 +1,26 @@
//! Protects fingerprint bytes when nested configuration keys are reordered.
use super::version_for_toml;
use pretty_assertions::assert_eq;
#[test]
fn fingerprint_preserves_nested_key_order_independence_and_existing_hash() {
for input in [
r#"rows = [{ z = 2, a = 1 }, { z = 4, a = 3 }]
[nested]
z = "text"
a = true
"#,
r#"rows = [{ a = 1, z = 2 }, { a = 3, z = 4 }]
[nested]
a = true
z = "text"
"#,
] {
let value = toml::from_str(input).expect("valid config");
assert_eq!(
version_for_toml(&value),
"sha256:a5e4c8fbdbc75d185b0f2b5291bfb2de10e8c523d6c89248876fd78a97c83176"
);
}
}