1use md5::{Digest, Md5};
6use uuid::Uuid;
7
8pub fn uuid() -> String {
10 Uuid::new_v4().to_string()
11}
12
13pub fn uuid_simple() -> String {
15 Uuid::new_v4().simple().to_string()
16}
17
18pub fn md5<S: AsRef<[u8]>>(data: S) -> String {
20 let mut hasher = Md5::new();
21 hasher.update(data);
22 let result = hasher.finalize();
23 format!("{:x}", result)
24}
25
26pub fn random_string(len: usize) -> String {
28 use rand::Rng;
29 const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
30 let mut rng = rand::rng();
31 (0..len)
32 .map(|_| {
33 let idx = rng.random_range(0..CHARSET.len());
34 CHARSET[idx] as char
35 })
36 .collect()
37}
38
39pub fn random_number_string(len: usize) -> String {
41 use rand::Rng;
42 let mut rng = rand::rng();
43 (0..len)
44 .map(|_| rng.random_range(0..10).to_string())
45 .collect()
46}
47
48pub fn camel_to_snake(s: &str) -> String {
50 let mut result = String::new();
51 for (i, c) in s.chars().enumerate() {
52 if c.is_uppercase() {
53 if i > 0 {
54 result.push('_');
55 }
56 result.push(c.to_lowercase().next().unwrap());
57 } else {
58 result.push(c);
59 }
60 }
61 result
62}
63
64pub fn snake_to_camel(s: &str) -> String {
66 let mut result = String::new();
67 let mut capitalize_next = false;
68
69 for c in s.chars() {
70 if c == '_' {
71 capitalize_next = true;
72 } else if capitalize_next {
73 result.push(c.to_uppercase().next().unwrap());
74 capitalize_next = false;
75 } else {
76 result.push(c);
77 }
78 }
79 result
80}
81
82pub fn capitalize(s: &str) -> String {
84 let mut chars = s.chars();
85 match chars.next() {
86 None => String::new(),
87 Some(first) => first.to_uppercase().chain(chars).collect(),
88 }
89}
90
91pub fn uncapitalize(s: &str) -> String {
93 let mut chars = s.chars();
94 match chars.next() {
95 None => String::new(),
96 Some(first) => first.to_lowercase().chain(chars).collect(),
97 }
98}
99
100pub fn timestamp() -> i64 {
102 chrono::Utc::now().timestamp()
103}
104
105pub fn timestamp_millis() -> i64 {
107 chrono::Utc::now().timestamp_millis()
108}
109
110pub fn format_datetime(dt: chrono::DateTime<chrono::Utc>, format: &str) -> String {
112 dt.format(format).to_string()
113}
114
115pub fn parse_datetime(s: &str, format: &str) -> Option<chrono::DateTime<chrono::Utc>> {
117 chrono::NaiveDateTime::parse_from_str(s, format)
118 .ok()
119 .and_then(|dt| dt.and_local_timezone(chrono::Utc).single())
120}
121
122pub mod ip {
124 pub fn is_private(ip: &str) -> bool {
126 if let Ok(addr) = ip.parse::<std::net::IpAddr>() {
127 match addr {
128 std::net::IpAddr::V4(v4) => {
129 v4.is_private() || v4.is_loopback() || v4.is_link_local()
130 }
131 std::net::IpAddr::V6(v6) => v6.is_loopback(),
132 }
133 } else {
134 false
135 }
136 }
137
138 pub fn local_ip() -> Option<String> {
140 local_ip_address::local_ip().ok().map(|ip| ip.to_string())
141 }
142}
143
144pub mod string {
146 pub fn is_blank(s: &str) -> bool {
148 s.trim().is_empty()
149 }
150
151 pub fn is_not_blank(s: &str) -> bool {
153 !is_blank(s)
154 }
155
156 pub fn truncate(s: &str, max_len: usize) -> &str {
158 if s.len() <= max_len {
159 s
160 } else {
161 &s[..max_len]
162 }
163 }
164
165 pub fn strip_html(s: &str) -> String {
167 let re = regex::Regex::new(r"<[^>]*>").unwrap();
168 re.replace_all(s, "").to_string()
169 }
170
171 pub fn replace_all(s: &str, from: &str, to: &str) -> String {
175 s.replace(from, to)
176 }
177
178 pub fn capitalize(s: &str) -> String {
180 let mut chars = s.chars();
181 match chars.next() {
182 None => String::new(),
183 Some(first) => first.to_uppercase().chain(chars).collect(),
184 }
185 }
186
187 pub fn uncapitalize(s: &str) -> String {
189 let mut chars = s.chars();
190 match chars.next() {
191 None => String::new(),
192 Some(first) => first.to_lowercase().chain(chars).collect(),
193 }
194 }
195}
196
197pub mod location {
201 use std::env;
202 use std::path::{Path, PathBuf};
203
204 pub fn get_executable_dir() -> Option<PathBuf> {
206 env::current_exe()
207 .ok()
208 .and_then(|exe| exe.parent().map(|p| p.to_path_buf()))
209 }
210
211 pub fn get_current_dir() -> Option<PathBuf> {
213 env::current_dir().ok()
214 }
215
216 pub fn get_project_root() -> Option<PathBuf> {
218 let mut current = env::current_dir().ok()?;
219
220 loop {
221 if current.join("Cargo.toml").exists() {
223 return Some(current);
224 }
225
226 if current.join("package.json").exists() {
228 return Some(current);
229 }
230
231 if current.join("dist").exists() {
233 return Some(current);
234 }
235
236 match current.parent() {
238 Some(parent) => current = parent.to_path_buf(),
239 None => break,
240 }
241 }
242
243 None
244 }
245
246 pub fn get_dist_path() -> Option<PathBuf> {
250 get_project_root().map(|root| root.join("dist"))
251 }
252
253 pub fn normalize_path(path: &Path) -> PathBuf {
255 path.canonicalize().unwrap_or_else(|_| path.to_path_buf())
256 }
257
258 pub fn join_path<P: AsRef<Path>>(base: &Path, parts: &[P]) -> PathBuf {
260 let mut result = base.to_path_buf();
261 for part in parts {
262 result = result.join(part);
263 }
264 result
265 }
266
267 pub fn relative_path(_from: &Path, _to: &Path) -> Option<PathBuf> {
271 None
274 }
275
276 pub fn path_exists(path: &Path) -> bool {
278 path.exists()
279 }
280
281 pub fn is_file(path: &Path) -> bool {
283 path.is_file()
284 }
285
286 pub fn is_dir(path: &Path) -> bool {
288 path.is_dir()
289 }
290
291 pub fn get_extension(path: &Path) -> Option<String> {
293 path.extension()
294 .and_then(|ext| ext.to_str())
295 .map(|s| s.to_string())
296 }
297
298 pub fn get_stem(path: &Path) -> Option<String> {
300 path.file_stem()
301 .and_then(|stem| stem.to_str())
302 .map(|s| s.to_string())
303 }
304
305 pub fn get_filename(path: &Path) -> Option<String> {
307 path.file_name()
308 .and_then(|name| name.to_str())
309 .map(|s| s.to_string())
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 #[test]
318 fn test_camel_to_snake() {
319 assert_eq!(camel_to_snake("userName"), "user_name");
320 assert_eq!(camel_to_snake("UserName"), "user_name");
321 assert_eq!(camel_to_snake("userId"), "user_id");
322 }
323
324 #[test]
325 fn test_snake_to_camel() {
326 assert_eq!(snake_to_camel("user_name"), "userName");
327 assert_eq!(snake_to_camel("user_id"), "userId");
328 }
329
330 #[test]
331 fn test_md5() {
332 assert_eq!(md5("hello"), "5d41402abc4b2a76b9719d911017c592");
333 }
334
335 #[test]
336 fn test_uuid() {
337 let id = uuid();
338 assert_eq!(id.len(), 36);
339 assert!(id.contains('-'));
340 }
341}