boundary_compiler/
digest.rs1use crate::error::JcsError;
8use blake3::Hash;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct ContentDigest(pub Hash);
13
14impl ContentDigest {
15 pub fn compute(value: &serde_json::Value) -> Result<Self, JcsError> {
17 use crate::canonicalizer::Canonicalizer;
18 let canonical_bytes = Canonicalizer::new().canonicalize_bytes(value)?;
19 Ok(Self(blake3::hash(&canonical_bytes)))
20 }
21
22 pub fn hex(&self) -> String {
24 self.0.to_hex().to_string()
25 }
26
27 pub fn as_bytes(&self) -> [u8; 32] {
29 *self.0.as_bytes()
30 }
31}
32
33impl std::fmt::Display for ContentDigest {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 write!(f, "{}", self.hex())
36 }
37}
38
39impl std::fmt::LowerHex for ContentDigest {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 write!(f, "{}", self.hex())
42 }
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48 use serde_json::json;
49
50 #[test]
51 fn test_digest_same_input_same_output() {
52 let val = json!({"b": 1, "a": 2});
53 let d1 = ContentDigest::compute(&val).unwrap();
54 let d2 = ContentDigest::compute(&val).unwrap();
55 assert_eq!(d1, d2);
56 assert_eq!(d1.hex(), d2.hex());
57 }
58
59 #[test]
60 fn test_digest_different_inputs_different_output() {
61 let val1 = json!({"a": 1});
62 let val2 = json!({"a": 2});
63 let d1 = ContentDigest::compute(&val1).unwrap();
64 let d2 = ContentDigest::compute(&val2).unwrap();
65 assert_ne!(d1, d2);
66 }
67
68 #[test]
69 fn test_digest_is_deterministic() {
70 let val = json!({"z": {"b": [1, 2], "a": null}, "a": true});
71 let d1 = ContentDigest::compute(&val).unwrap();
72 for _ in 0..10 {
74 let d2 = ContentDigest::compute(&val).unwrap();
75 assert_eq!(d1, d2);
76 }
77 }
78
79 #[test]
80 fn test_digest_display() {
81 let val = json!({"x": 1});
82 let d = ContentDigest::compute(&val).unwrap();
83 let display = format!("{}", d);
84 let hex = format!("{:x}", d);
85 assert_eq!(display, hex);
86 }
87}