Skip to main content

ferrox_utils/
lib.rs

1use chrono::{DateTime, TimeZone, Utc};
2use convert_case::{Case, Casing};
3use time::OffsetDateTime;
4use uuid::Uuid;
5
6pub mod date {
7    use super::*;
8
9    /// Returns the current time strictly in UTC. 
10    /// All databases in Ferrox MUST store dates in UTC.
11    pub fn now_utc() -> DateTime<Utc> {
12        Utc::now()
13    }
14
15    /// Converts a given UTC DateTime to a formatted GMT string for the Frontend.
16    pub fn to_gmt_string(dt: DateTime<Utc>) -> String {
17        dt.format("%a, %d %b %Y %H:%M:%S GMT").to_string()
18    }
19}
20
21pub mod string {
22    use super::*;
23
24    pub trait StringExt {
25        fn to_camel_case(&self) -> String;
26        fn to_snake_case(&self) -> String;
27        fn to_kebab_case(&self) -> String;
28        fn mask(&self, keep_start: usize, keep_end: usize) -> String;
29    }
30
31    impl StringExt for String {
32        fn to_camel_case(&self) -> String {
33            self.to_case(Case::Camel)
34        }
35
36        fn to_snake_case(&self) -> String {
37            self.to_case(Case::Snake)
38        }
39
40        fn to_kebab_case(&self) -> String {
41            self.to_case(Case::Kebab)
42        }
43
44        fn mask(&self, keep_start: usize, keep_end: usize) -> String {
45            if self.len() <= keep_start + keep_end {
46                return self.clone();
47            }
48            
49            let start = &self[..keep_start];
50            let end = &self[self.len() - keep_end..];
51            let masked_len = self.len() - keep_start - keep_end;
52            let mask_chars = "*".repeat(masked_len);
53            
54            format!("{}{}{}", start, mask_chars, end)
55        }
56    }
57}
58
59/// Generates a secure, time-ordered UUID v7 string.
60pub fn generate_uuid() -> String {
61    Uuid::now_v7().to_string()
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use super::date::*;
68    use super::string::*;
69
70    #[test]
71    fn test_generate_uuid() {
72        let id1 = generate_uuid();
73        let id2 = generate_uuid();
74        
75        assert_eq!(id1.len(), 36);
76        assert_ne!(id1, id2);
77    }
78
79    #[test]
80    fn test_date_utilities() {
81        let utc = now_utc();
82        let gmt_str = to_gmt_string(utc);
83        assert!(gmt_str.ends_with("GMT"));
84    }
85
86    #[test]
87    fn test_string_casing() {
88        let original = String::from("hello world test");
89        assert_eq!(original.to_camel_case(), "helloWorldTest");
90        assert_eq!(original.to_snake_case(), "hello_world_test");
91        assert_eq!(original.to_kebab_case(), "hello-world-test");
92    }
93
94    #[test]
95    fn test_string_masking() {
96        let email = String::from("admin@antigravity.com");
97        let masked = email.mask(3, 4); // "adm**************.com"
98        
99        assert!(masked.starts_with("adm"));
100        assert!(masked.ends_with(".com"));
101        assert!(masked.contains("*"));
102        assert_eq!(masked.len(), email.len());
103        
104        // Edge case: string too short
105        let short = String::from("a");
106        assert_eq!(short.mask(3, 4), "a");
107        
108        // Exact length
109        let exact = String::from("abcdefg");
110        assert_eq!(exact.mask(3, 4), "abcdefg");
111    }
112}