1use serde::Serialize;
8use sha2::{Digest, Sha256};
9
10#[must_use]
30pub fn content_hash<T: Serialize>(value: &T) -> String {
31 let json = serde_json::to_string(value).expect("serialization should not fail");
32 let mut hasher = Sha256::new();
33 hasher.update(json.as_bytes());
34 format!("{:x}", hasher.finalize())
35}
36
37#[must_use]
45pub fn has_changed(current_hash: &str, stored_hash: Option<&str>) -> bool {
46 stored_hash.map_or(true, |h| h != current_hash)
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use serde::Serialize;
53
54 #[derive(Serialize)]
55 struct TestRecord {
56 id: String,
57 value: i32,
58 }
59
60 #[test]
61 fn test_content_hash_deterministic() {
62 let record = TestRecord {
63 id: "test_1".into(),
64 value: 42,
65 };
66
67 let hash1 = content_hash(&record);
68 let hash2 = content_hash(&record);
69
70 assert_eq!(hash1, hash2);
71 assert_eq!(hash1.len(), 64); }
73
74 #[test]
75 fn test_content_hash_changes_with_content() {
76 let record1 = TestRecord {
77 id: "test_1".into(),
78 value: 42,
79 };
80 let record2 = TestRecord {
81 id: "test_1".into(),
82 value: 43, };
84
85 let hash1 = content_hash(&record1);
86 let hash2 = content_hash(&record2);
87
88 assert_ne!(hash1, hash2);
89 }
90
91 #[test]
92 fn test_has_changed_no_stored_hash() {
93 assert!(has_changed("abc123", None));
94 }
95
96 #[test]
97 fn test_has_changed_different_hash() {
98 assert!(has_changed("abc123", Some("xyz789")));
99 }
100
101 #[test]
102 fn test_has_changed_same_hash() {
103 assert!(!has_changed("abc123", Some("abc123")));
104 }
105}