Skip to main content

libbrat_grite/
id.rs

1use chrono::Utc;
2use rand::Rng;
3
4/// Generate a convoy ID: c-YYYYMMDD-<4hex>
5pub fn generate_convoy_id() -> String {
6    let date = Utc::now().format("%Y%m%d");
7    let hex: u16 = rand::thread_rng().gen();
8    format!("c-{}-{:04x}", date, hex)
9}
10
11/// Generate a task ID: t-YYYYMMDD-<4hex>
12pub fn generate_task_id() -> String {
13    let date = Utc::now().format("%Y%m%d");
14    let hex: u16 = rand::thread_rng().gen();
15    format!("t-{}-{:04x}", date, hex)
16}
17
18/// Generate a session ID: s-YYYYMMDD-<4hex>
19pub fn generate_session_id() -> String {
20    let date = Utc::now().format("%Y%m%d");
21    let hex: u16 = rand::thread_rng().gen();
22    format!("s-{}-{:04x}", date, hex)
23}
24
25/// Parse a convoy ID, returning (date_str, hex_suffix) if valid.
26pub fn parse_convoy_id(id: &str) -> Option<(&str, &str)> {
27    let parts: Vec<&str> = id.splitn(3, '-').collect();
28    if parts.len() == 3 && parts[0] == "c" && parts[1].len() == 8 && parts[2].len() == 4 {
29        Some((parts[1], parts[2]))
30    } else {
31        None
32    }
33}
34
35/// Parse a task ID, returning (date_str, hex_suffix) if valid.
36pub fn parse_task_id(id: &str) -> Option<(&str, &str)> {
37    let parts: Vec<&str> = id.splitn(3, '-').collect();
38    if parts.len() == 3 && parts[0] == "t" && parts[1].len() == 8 && parts[2].len() == 4 {
39        Some((parts[1], parts[2]))
40    } else {
41        None
42    }
43}
44
45/// Parse a session ID, returning (date_str, hex_suffix) if valid.
46pub fn parse_session_id(id: &str) -> Option<(&str, &str)> {
47    let parts: Vec<&str> = id.splitn(3, '-').collect();
48    if parts.len() == 3 && parts[0] == "s" && parts[1].len() == 8 && parts[2].len() == 4 {
49        Some((parts[1], parts[2]))
50    } else {
51        None
52    }
53}
54
55/// Check if a string is a valid convoy ID.
56pub fn is_valid_convoy_id(id: &str) -> bool {
57    parse_convoy_id(id).is_some()
58}
59
60/// Check if a string is a valid task ID.
61pub fn is_valid_task_id(id: &str) -> bool {
62    parse_task_id(id).is_some()
63}
64
65/// Check if a string is a valid session ID.
66pub fn is_valid_session_id(id: &str) -> bool {
67    parse_session_id(id).is_some()
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn test_generate_convoy_id_format() {
76        let id = generate_convoy_id();
77        assert!(id.starts_with("c-"));
78        assert_eq!(id.len(), 15); // c-YYYYMMDD-XXXX (1+1+8+1+4=15)
79        assert!(is_valid_convoy_id(&id));
80    }
81
82    #[test]
83    fn test_generate_task_id_format() {
84        let id = generate_task_id();
85        assert!(id.starts_with("t-"));
86        assert_eq!(id.len(), 15); // t-YYYYMMDD-XXXX (1+1+8+1+4=15)
87        assert!(is_valid_task_id(&id));
88    }
89
90    #[test]
91    fn test_generate_session_id_format() {
92        let id = generate_session_id();
93        assert!(id.starts_with("s-"));
94        assert_eq!(id.len(), 15); // s-YYYYMMDD-XXXX (1+1+8+1+4=15)
95        assert!(is_valid_session_id(&id));
96    }
97
98    #[test]
99    fn test_parse_convoy_id() {
100        let (date, hex) = parse_convoy_id("c-20250116-a2f9").unwrap();
101        assert_eq!(date, "20250116");
102        assert_eq!(hex, "a2f9");
103    }
104
105    #[test]
106    fn test_parse_task_id() {
107        let (date, hex) = parse_task_id("t-20250116-3a2c").unwrap();
108        assert_eq!(date, "20250116");
109        assert_eq!(hex, "3a2c");
110    }
111
112    #[test]
113    fn test_invalid_ids() {
114        assert!(parse_convoy_id("invalid").is_none());
115        assert!(parse_convoy_id("t-20250116-a2f9").is_none()); // wrong prefix
116        assert!(parse_convoy_id("c-2025011-a2f9").is_none()); // short date
117        assert!(parse_convoy_id("c-20250116-a2f").is_none()); // short hex
118    }
119
120    #[test]
121    fn test_uniqueness() {
122        let ids: Vec<String> = (0..100).map(|_| generate_convoy_id()).collect();
123        let unique: std::collections::HashSet<_> = ids.iter().collect();
124        // Should have mostly unique IDs (date is same, but hex should differ)
125        assert!(unique.len() > 90);
126    }
127}