1use sha2::{Digest, Sha256};
2
3pub fn node_id(hash_input: &serde_json::Value) -> String {
12 let canonical = canonical_json_bytes(hash_input);
13 let digest = Sha256::digest(&canonical);
14 hex::encode(digest)
15}
16
17pub fn canonical_json_bytes(value: &serde_json::Value) -> Vec<u8> {
22 fn sort_keys(v: &serde_json::Value) -> serde_json::Value {
23 match v {
24 serde_json::Value::Object(map) => {
25 let mut keys: Vec<&String> = map.keys().collect();
26 keys.sort();
27 let sorted = keys
28 .into_iter()
29 .map(|k| (k.clone(), sort_keys(&map[k])))
30 .collect();
31 serde_json::Value::Object(sorted)
32 }
33 serde_json::Value::Array(arr) => {
34 serde_json::Value::Array(arr.iter().map(sort_keys).collect())
35 }
36 other => other.clone(),
37 }
38 }
39 let sorted = sort_keys(value);
40 serde_json::to_vec(&sorted).expect("valid JSON value cannot fail to serialize")
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47 use serde_json::json;
48
49 #[test]
50 fn key_order_is_stable() {
51 let a = json!({ "z": 1, "a": 2, "m": 3 });
52 let b = json!({ "m": 3, "z": 1, "a": 2 });
53 assert_eq!(canonical_json_bytes(&a), canonical_json_bytes(&b));
54 }
55
56 #[test]
57 fn nested_objects_sorted() {
58 let v = json!({ "b": { "z": 1, "a": 2 }, "a": 1 });
59 let bytes = canonical_json_bytes(&v);
60 let s = std::str::from_utf8(&bytes).unwrap();
61 assert!(s.find('"').is_some());
63 let parsed: serde_json::Value = serde_json::from_str(s).unwrap();
64 let obj = parsed.as_object().unwrap();
65 let keys: Vec<&String> = obj.keys().collect();
66 assert_eq!(keys[0], "a");
67 assert_eq!(keys[1], "b");
68 }
69
70 #[test]
71 fn node_id_is_hex_sha256() {
72 let id = node_id(&json!({ "type": "intent", "text": "hello" }));
73 assert_eq!(id.len(), 64);
74 assert!(id.chars().all(|c| c.is_ascii_hexdigit()));
75 }
76
77 #[test]
78 fn same_content_same_id() {
79 let v = json!({ "type": "intent", "text": "add JWT verification" });
80 assert_eq!(node_id(&v), node_id(&v));
81 }
82}