1use std::collections::HashSet;
2
3#[must_use]
4pub fn fingerprint(origin: &str, standard: &str, url: &str) -> String {
5 format!("{origin}\0{standard}\0{url}")
6}
7
8pub struct DedupSet {
9 seen: HashSet<String>,
10}
11
12impl DedupSet {
13 #[must_use]
14 pub fn new() -> Self {
15 Self {
16 seen: HashSet::new(),
17 }
18 }
19
20 pub fn is_new(&mut self, origin: &str, standard: &str, url: &str) -> bool {
21 self.seen.insert(fingerprint(origin, standard, url))
22 }
23}
24
25impl Default for DedupSet {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31#[cfg(test)]
32mod tests {
33 use super::*;
34
35 #[test]
36 fn fingerprint_format() {
37 let fp = fingerprint("https://example.com", "ard", "https://example.com/.well-known/ard.json");
38 assert_eq!(fp, "https://example.com\0ard\0https://example.com/.well-known/ard.json");
39 }
40
41 #[test]
42 fn different_fields_differ() {
43 let a = fingerprint("https://a.com", "ard", "/x");
44 let b = fingerprint("https://b.com", "ard", "/x");
45 assert_ne!(a, b);
46 }
47
48 #[test]
49 fn dedup_first_is_new() {
50 let mut ds = DedupSet::new();
51 assert!(ds.is_new("https://example.com", "ard", "/x"));
52 }
53
54 #[test]
55 fn dedup_second_is_not_new() {
56 let mut ds = DedupSet::new();
57 ds.is_new("https://example.com", "ard", "/x");
58 assert!(!ds.is_new("https://example.com", "ard", "/x"));
59 }
60
61 #[test]
62 fn dedup_different_standard_is_new() {
63 let mut ds = DedupSet::new();
64 ds.is_new("https://example.com", "ard", "/x");
65 assert!(ds.is_new("https://example.com", "other", "/x"));
66 }
67}