1use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::fmt;
9use std::time::{SystemTime, UNIX_EPOCH};
10use uuid::Uuid;
11use crate::errors::{Result, RustisanError};
12
13pub fn random_string(length: usize) -> String {
15 use rand::Rng;
16 const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
17 let mut rng = rand::thread_rng();
18
19 (0..length)
20 .map(|_| {
21 let idx = rng.gen_range(0..CHARSET.len());
22 CHARSET[idx] as char
23 })
24 .collect()
25}
26
27pub fn uuid() -> String {
29 Uuid::new_v4().to_string()
30}
31
32pub fn timestamp() -> u64 {
34 SystemTime::now()
35 .duration_since(UNIX_EPOCH)
36 .unwrap_or_default()
37 .as_secs()
38}
39
40pub fn timestamp_millis() -> u64 {
42 SystemTime::now()
43 .duration_since(UNIX_EPOCH)
44 .unwrap_or_default()
45 .as_millis() as u64
46}
47
48pub fn timestamp_to_datetime(timestamp: u64) -> DateTime<Utc> {
50 DateTime::from_timestamp(timestamp as i64, 0).unwrap_or_else(|| Utc::now())
51}
52
53pub fn slugify(input: &str) -> String {
55 input
56 .chars()
57 .map(|c| {
58 if c.is_alphanumeric() {
59 c.to_ascii_lowercase()
60 } else if c.is_whitespace() || c == '-' || c == '_' {
61 '-'
62 } else {
63 '\0'
64 }
65 })
66 .filter(|&c| c != '\0')
67 .collect::<String>()
68 .split('-')
69 .filter(|s| !s.is_empty())
70 .collect::<Vec<&str>>()
71 .join("-")
72}
73
74pub fn camel_to_snake(input: &str) -> String {
76 let mut result = String::new();
77 for (i, ch) in input.chars().enumerate() {
78 if i > 0 && ch.is_uppercase() {
79 result.push('_');
80 }
81 result.push(ch.to_lowercase().next().unwrap_or(ch));
82 }
83 result
84}
85
86pub fn snake_to_camel(input: &str) -> String {
88 let mut result = String::new();
89 let mut capitalize_next = false;
90
91 for ch in input.chars() {
92 if ch == '_' {
93 capitalize_next = true;
94 } else if capitalize_next {
95 result.push(ch.to_uppercase().next().unwrap_or(ch));
96 capitalize_next = false;
97 } else {
98 result.push(ch);
99 }
100 }
101
102 result
103}
104
105pub fn pascal_to_snake(input: &str) -> String {
107 camel_to_snake(input)
108}
109
110pub fn snake_to_pascal(input: &str) -> String {
112 input
113 .split('_')
114 .map(|word| {
115 let mut chars = word.chars();
116 match chars.next() {
117 None => String::new(),
118 Some(first) => first.to_uppercase().collect::<String>() + &chars.as_str(),
119 }
120 })
121 .collect()
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_random_string() {
130 let s = random_string(10);
131 assert_eq!(s.len(), 10);
132 }
133
134 #[test]
135 fn test_uuid() {
136 let u = uuid();
137 assert!(u.len() > 0);
138 assert!(u.contains('-'));
139 }
140
141 #[test]
142 fn test_slugify() {
143 assert_eq!(slugify("Hello World"), "hello-world");
144 assert_eq!(slugify("Test Multiple Spaces"), "test-multiple-spaces");
145 assert_eq!(slugify("Special!@#Characters"), "specialcharacters");
146 }
147
148 #[test]
149 fn test_camel_to_snake() {
150 assert_eq!(camel_to_snake("camelCase"), "camel_case");
151 assert_eq!(camel_to_snake("HTTPRequest"), "h_t_t_p_request");
152 assert_eq!(camel_to_snake("simple"), "simple");
153 }
154
155 #[test]
156 fn test_snake_to_camel() {
157 assert_eq!(snake_to_camel("snake_case"), "snakeCase");
158 assert_eq!(snake_to_camel("simple"), "simple");
159 assert_eq!(snake_to_camel("multiple_words_here"), "multipleWordsHere");
160 }
161
162 #[test]
163 fn test_snake_to_pascal() {
164 assert_eq!(snake_to_pascal("snake_case"), "SnakeCase");
165 assert_eq!(snake_to_pascal("simple"), "Simple");
166 assert_eq!(snake_to_pascal("multiple_words_here"), "MultipleWordsHere");
167 }
168
169 #[test]
170 fn test_pascal_to_snake() {
171 assert_eq!(pascal_to_snake("PascalCase"), "pascal_case");
172 assert_eq!(pascal_to_snake("HTTPRequest"), "h_t_t_p_request");
173 }
174}