Skip to main content

karbon_framework/util/
random.rs

1use rand::RngExt;
2
3/// Random value generation helpers
4pub struct RandomHelper;
5
6impl RandomHelper {
7    /// Generate a random hex token of given byte length
8    /// e.g., token(16) => "a3f2b1c4d5e6f7a8b9c0d1e2f3a4b5c6" (32 hex chars)
9    pub fn token(byte_length: usize) -> String {
10        let mut rng = rand::rng();
11        let bytes: Vec<u8> = (0..byte_length).map(|_| rng.random()).collect();
12        bytes.iter().map(|b| format!("{:02x}", b)).collect()
13    }
14
15    /// Generate a random alphanumeric string of given length
16    pub fn alphanumeric(length: usize) -> String {
17        const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
18        let mut rng = rand::rng();
19        (0..length)
20            .map(|_| {
21                let idx = rng.random_range(0..CHARSET.len());
22                CHARSET[idx] as char
23            })
24            .collect()
25    }
26
27    /// Generate a numeric OTP code of given length
28    /// e.g., otp(6) => "482951"
29    pub fn otp(length: usize) -> String {
30        let mut rng = rand::rng();
31        (0..length)
32            .map(|_| {
33                let digit: u8 = rng.random_range(0..10);
34                char::from(b'0' + digit)
35            })
36            .collect()
37    }
38
39    /// Generate a random integer within a range (inclusive)
40    pub fn int(min: i64, max: i64) -> i64 {
41        let mut rng = rand::rng();
42        rng.random_range(min..=max)
43    }
44
45    /// Pick a random element from a slice
46    pub fn pick<'a, T>(items: &'a [T]) -> Option<&'a T> {
47        if items.is_empty() {
48            return None;
49        }
50        let mut rng = rand::rng();
51        let idx = rng.random_range(0..items.len());
52        Some(&items[idx])
53    }
54
55    /// Shuffle a vector in place and return it
56    pub fn shuffle<T>(mut items: Vec<T>) -> Vec<T> {
57        let mut rng = rand::rng();
58        let len = items.len();
59        for i in (1..len).rev() {
60            let j = rng.random_range(0..=i);
61            items.swap(i, j);
62        }
63        items
64    }
65
66    /// Generate a UUID v4 string
67    pub fn uuid() -> String {
68        uuid::Uuid::new_v4().to_string()
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn test_token_length() {
78        let t = RandomHelper::token(16);
79        assert_eq!(t.len(), 32); // 16 bytes = 32 hex chars
80        assert!(t.chars().all(|c| c.is_ascii_hexdigit()));
81    }
82
83    #[test]
84    fn test_token_uniqueness() {
85        let a = RandomHelper::token(16);
86        let b = RandomHelper::token(16);
87        assert_ne!(a, b);
88    }
89
90    #[test]
91    fn test_alphanumeric_length() {
92        let s = RandomHelper::alphanumeric(20);
93        assert_eq!(s.len(), 20);
94        assert!(s.chars().all(|c| c.is_alphanumeric()));
95    }
96
97    #[test]
98    fn test_otp_length_and_digits() {
99        let code = RandomHelper::otp(6);
100        assert_eq!(code.len(), 6);
101        assert!(code.chars().all(|c| c.is_ascii_digit()));
102    }
103
104    #[test]
105    fn test_otp_4_digits() {
106        let code = RandomHelper::otp(4);
107        assert_eq!(code.len(), 4);
108    }
109
110    #[test]
111    fn test_int_range() {
112        for _ in 0..100 {
113            let n = RandomHelper::int(1, 10);
114            assert!((1..=10).contains(&n));
115        }
116    }
117
118    #[test]
119    fn test_pick() {
120        let items = vec!["a", "b", "c"];
121        let picked = RandomHelper::pick(&items);
122        assert!(picked.is_some());
123        assert!(items.contains(picked.unwrap()));
124    }
125
126    #[test]
127    fn test_pick_empty() {
128        let items: Vec<i32> = vec![];
129        assert!(RandomHelper::pick(&items).is_none());
130    }
131
132    #[test]
133    fn test_shuffle_preserves_elements() {
134        let items = vec![1, 2, 3, 4, 5];
135        let shuffled = RandomHelper::shuffle(items.clone());
136        assert_eq!(shuffled.len(), items.len());
137        for item in &items {
138            assert!(shuffled.contains(item));
139        }
140    }
141
142    #[test]
143    fn test_uuid_format() {
144        let id = RandomHelper::uuid();
145        assert_eq!(id.len(), 36);
146        assert_eq!(id.chars().filter(|&c| c == '-').count(), 4);
147    }
148}