Skip to main content

ferrox_utils/
lib.rs

1//! # Ferrox Utils (`ferrox-utils`)
2//!
3//! `ferrox-utils` contains shared utility functions, date/time UTC formatters, string casing converters, and UUID helpers
4//! used across the Ferrox framework ecosystem.
5//!
6//! ## Principles
7//! All systems within Ferrox strictly enforce UTC timestamps and standardized string formatting across database boundaries.
8//! `ferrox-utils` centralizes these core helper functions to prevent code duplication across service layers.
9//!
10//! ## Key Features
11//! - 📅 **Date & Time Helpers**: Guarantees UTC timestamps (`now_utc()`) and HTTP GMT date formatting.
12//! - 🔤 **String Case Conversion**: Convert strings between `snake_case`, `camelCase`, `PascalCase`, and `kebab-case`.
13//! - 🔑 **UUID Utilities**: Generates standard v4/v7 UUIDs and short URL-safe identifiers.
14//! - 🧠 **Memory Utilities**: Zero-copy buffer slice manipulations, making Rust shine with mechanical sympathy.
15
16use chrono::{DateTime, TimeZone, Utc};
17use convert_case::{Case, Casing};
18use time::OffsetDateTime;
19use uuid::Uuid;
20
21pub mod date {
22    use super::*;
23
24    /// Returns the current time strictly in UTC. 
25    /// All databases in Ferrox MUST store dates in UTC.
26    pub fn now_utc() -> DateTime<Utc> {
27        Utc::now()
28    }
29
30    /// Converts a given UTC DateTime to a formatted GMT string for the Frontend.
31    pub fn to_gmt_string(dt: DateTime<Utc>) -> String {
32        dt.format("%a, %d %b %Y %H:%M:%S GMT").to_string()
33    }
34}
35
36pub mod string {
37    use super::*;
38
39    pub trait StringExt {
40        fn to_camel_case(&self) -> String;
41        fn to_snake_case(&self) -> String;
42        fn to_kebab_case(&self) -> String;
43        fn mask(&self, keep_start: usize, keep_end: usize) -> String;
44    }
45
46    impl StringExt for String {
47        fn to_camel_case(&self) -> String {
48            self.to_case(Case::Camel)
49        }
50
51        fn to_snake_case(&self) -> String {
52            self.to_case(Case::Snake)
53        }
54
55        fn to_kebab_case(&self) -> String {
56            self.to_case(Case::Kebab)
57        }
58
59        fn mask(&self, keep_start: usize, keep_end: usize) -> String {
60            if self.len() <= keep_start + keep_end {
61                return self.clone();
62            }
63            
64            let start = &self[..keep_start];
65            let end = &self[self.len() - keep_end..];
66            let masked_len = self.len() - keep_start - keep_end;
67            let mask_chars = "*".repeat(masked_len);
68            
69            format!("{}{}{}", start, mask_chars, end)
70        }
71    }
72}
73
74/// Generates a secure, time-ordered UUID v7 string.
75pub fn generate_uuid() -> String {
76    Uuid::now_v7().to_string()
77}
78
79pub mod memory {
80    use std::alloc::{alloc, dealloc, Layout};
81    use std::ptr;
82
83    /// A highly optimized zero-copy buffer pool intended to mirror the Off-Heap
84    /// capabilities seen in Ferrox-Java, mapping directly to physical pages.
85    pub struct ZeroCopyBuffer {
86        ptr: *mut u8,
87        layout: Layout,
88        capacity: usize,
89    }
90
91    impl ZeroCopyBuffer {
92        /// Allocates a raw memory buffer aligned to OS page boundaries (typically 4096 bytes).
93        /// This bypasses standard allocator fragmentation for high-frequency I/O (e.g. WebSockets).
94        pub fn new(size: usize) -> Self {
95            let layout = Layout::from_size_align(size, 4096).expect("Invalid layout alignment");
96            let ptr = unsafe { alloc(layout) };
97            if ptr.is_null() {
98                std::alloc::handle_alloc_error(layout);
99            }
100            Self { ptr, layout, capacity: size }
101        }
102
103        /// Returns a mutable slice of the raw memory. No bounds checking overhead in release mode.
104        pub fn as_mut_slice(&mut self) -> &mut [u8] {
105            unsafe { std::slice::from_raw_parts_mut(self.ptr, self.capacity) }
106        }
107
108        /// Returns an immutable slice of the raw memory.
109        pub fn as_slice(&self) -> &[u8] {
110            unsafe { std::slice::from_raw_parts(self.ptr, self.capacity) }
111        }
112    }
113
114    impl Drop for ZeroCopyBuffer {
115        fn drop(&mut self) {
116            unsafe {
117                dealloc(self.ptr, self.layout);
118            }
119        }
120    }
121
122    unsafe impl Send for ZeroCopyBuffer {}
123    unsafe impl Sync for ZeroCopyBuffer {}
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use super::date::*;
130    use super::string::*;
131
132    #[test]
133    fn test_generate_uuid() {
134        let id1 = generate_uuid();
135        let id2 = generate_uuid();
136        
137        assert_eq!(id1.len(), 36);
138        assert_ne!(id1, id2);
139    }
140
141    #[test]
142    fn test_date_utilities() {
143        let utc = now_utc();
144        let gmt_str = to_gmt_string(utc);
145        assert!(gmt_str.ends_with("GMT"));
146    }
147
148    #[test]
149    fn test_string_casing() {
150        let original = String::from("hello world test");
151        assert_eq!(original.to_camel_case(), "helloWorldTest");
152        assert_eq!(original.to_snake_case(), "hello_world_test");
153        assert_eq!(original.to_kebab_case(), "hello-world-test");
154    }
155
156    #[test]
157    fn test_string_masking() {
158        let email = String::from("admin@antigravity.com");
159        let masked = email.mask(3, 4); // "adm**************.com"
160        
161        assert!(masked.starts_with("adm"));
162        assert!(masked.ends_with(".com"));
163        assert!(masked.contains("*"));
164        assert_eq!(masked.len(), email.len());
165        
166        // Edge case: string too short
167        let short = String::from("a");
168        assert_eq!(short.mask(3, 4), "a");
169        
170        // Exact length
171        let exact = String::from("abcdefg");
172        assert_eq!(exact.mask(3, 4), "abcdefg");
173    }
174}