Skip to main content

max / goingson

1.8 KB · 58 lines History Blame Raw
1 //! Deterministic email ID generation using UUID v5.
2 //!
3 //! Emails synced from IMAP/JMAP get a UUID derived from their Message-ID header,
4 //! so the same email produces the same ID on every device. Manually created emails
5 //! (no Message-ID) fall back to random UUIDs.
6
7 use crate::id_types::EmailId;
8 use uuid::Uuid;
9
10 /// Fixed namespace UUID for GoingsOn email IDs (generated once, never changes).
11 pub const GOINGSON_EMAIL_NS: Uuid = Uuid::from_bytes([
12 0x7a, 0x3b, 0x8c, 0x2d, 0x4e, 0x5f, 0x6a, 0x1b,
13 0x9c, 0x0d, 0x8e, 0x7f, 0xa2, 0xb3, 0xc4, 0xd5,
14 ]);
15
16 /// Generate a deterministic email ID from a Message-ID header.
17 ///
18 /// - `Some(message_id)` → UUID v5 from namespace + message_id bytes
19 /// - `None` → random UUID v4 (for manually created emails without Message-ID)
20 pub fn deterministic_email_id(message_id: Option<&str>) -> EmailId {
21 match message_id {
22 Some(mid) => EmailId::from(Uuid::new_v5(&GOINGSON_EMAIL_NS, mid.as_bytes())),
23 None => EmailId::new(),
24 }
25 }
26
27 #[cfg(test)]
28 mod tests {
29 use super::*;
30
31 #[test]
32 fn same_message_id_same_uuid() {
33 let a = deterministic_email_id(Some("<abc@example.com>"));
34 let b = deterministic_email_id(Some("<abc@example.com>"));
35 assert_eq!(a, b);
36 }
37
38 #[test]
39 fn different_message_ids_different_uuids() {
40 let a = deterministic_email_id(Some("<abc@example.com>"));
41 let b = deterministic_email_id(Some("<def@example.com>"));
42 assert_ne!(a, b);
43 }
44
45 #[test]
46 fn none_gives_random_uuid() {
47 let a = deterministic_email_id(None);
48 let b = deterministic_email_id(None);
49 assert_ne!(a, b);
50 }
51
52 #[test]
53 fn v5_uuid_version() {
54 let id = deterministic_email_id(Some("<test@example.com>"));
55 assert_eq!(id.get_version_num(), 5);
56 }
57 }
58