diffguard_core/
fingerprint.rs1use diffguard_types::Finding;
7use sha2::{Digest, Sha256};
8
9pub fn compute_fingerprint(f: &Finding) -> String {
14 let input = format!("{}:{}:{}:{}", f.rule_id, f.path, f.line, f.match_text);
15 compute_fingerprint_raw(&input)
16}
17
18pub fn compute_fingerprint_raw(input: &str) -> String {
22 let hash = Sha256::digest(input.as_bytes());
23 hex::encode(hash)
24}
25
26#[cfg(test)]
27mod tests {
28 use super::*;
29 use diffguard_types::Severity;
30
31 fn test_finding() -> Finding {
32 Finding {
33 rule_id: "rust.no_unwrap".to_string(),
34 severity: Severity::Error,
35 message: "Avoid unwrap".to_string(),
36 path: "src/lib.rs".to_string(),
37 line: 42,
38 column: Some(10),
39 match_text: ".unwrap()".to_string(),
40 snippet: "let x = foo.unwrap();".to_string(),
41 }
42 }
43
44 #[test]
45 fn fingerprint_is_64_hex_chars() {
46 let f = test_finding();
47 let fp = compute_fingerprint(&f);
48 assert_eq!(fp.len(), 64);
49 assert!(fp.chars().all(|c| c.is_ascii_hexdigit()));
50 }
51
52 #[test]
53 fn fingerprint_is_stable() {
54 let f = test_finding();
55 let fp1 = compute_fingerprint(&f);
56 let fp2 = compute_fingerprint(&f);
57 assert_eq!(fp1, fp2);
58 }
59
60 #[test]
61 fn fingerprint_differs_for_different_rule_id() {
62 let f1 = test_finding();
63 let mut f2 = test_finding();
64 f2.rule_id = "rust.no_dbg".to_string();
65
66 assert_ne!(compute_fingerprint(&f1), compute_fingerprint(&f2));
67 }
68
69 #[test]
70 fn fingerprint_differs_for_different_path() {
71 let f1 = test_finding();
72 let mut f2 = test_finding();
73 f2.path = "src/main.rs".to_string();
74
75 assert_ne!(compute_fingerprint(&f1), compute_fingerprint(&f2));
76 }
77
78 #[test]
79 fn fingerprint_differs_for_different_line() {
80 let f1 = test_finding();
81 let mut f2 = test_finding();
82 f2.line = 100;
83
84 assert_ne!(compute_fingerprint(&f1), compute_fingerprint(&f2));
85 }
86
87 #[test]
88 fn fingerprint_differs_for_different_match_text() {
89 let f1 = test_finding();
90 let mut f2 = test_finding();
91 f2.match_text = ".expect()".to_string();
92
93 assert_ne!(compute_fingerprint(&f1), compute_fingerprint(&f2));
94 }
95
96 #[test]
97 fn fingerprint_ignores_severity() {
98 let f1 = test_finding();
99 let mut f2 = test_finding();
100 f2.severity = Severity::Warn;
101
102 assert_eq!(compute_fingerprint(&f1), compute_fingerprint(&f2));
104 }
105
106 #[test]
107 fn fingerprint_ignores_message() {
108 let f1 = test_finding();
109 let mut f2 = test_finding();
110 f2.message = "Different message".to_string();
111
112 assert_eq!(compute_fingerprint(&f1), compute_fingerprint(&f2));
114 }
115
116 #[test]
117 fn snapshot_fingerprint_value() {
118 let f = test_finding();
119 let fp = compute_fingerprint(&f);
120 insta::assert_snapshot!(fp, @"d559ee3767f8ccda27b477039711c881d44c366e3bd8ea119649746bdff1a0b8");
122 }
123
124 #[test]
125 fn compute_fingerprint_raw_produces_64_hex() {
126 let fp = super::compute_fingerprint_raw("test:input");
127 assert_eq!(fp.len(), 64);
128 assert!(fp.chars().all(|c| c.is_ascii_hexdigit()));
129 }
130}