use md5::{Digest, Md5};
use uuid::Uuid;
pub fn uuid() -> String {
Uuid::new_v4().to_string()
}
pub fn uuid_simple() -> String {
Uuid::new_v4().simple().to_string()
}
pub fn md5<S: AsRef<[u8]>>(data: S) -> String {
let mut hasher = Md5::new();
hasher.update(data);
let result = hasher.finalize();
format!("{:x}", result)
}
pub fn random_string(len: usize) -> String {
use rand::Rng;
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let mut rng = rand::rng();
(0..len)
.map(|_| {
let idx = rng.random_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect()
}
pub fn random_number_string(len: usize) -> String {
use rand::Rng;
let mut rng = rand::rng();
(0..len)
.map(|_| rng.random_range(0..10).to_string())
.collect()
}
pub fn camel_to_snake(s: &str) -> String {
let mut result = String::new();
for (i, c) in s.chars().enumerate() {
if c.is_uppercase() {
if i > 0 {
result.push('_');
}
result.push(c.to_lowercase().next().unwrap());
} else {
result.push(c);
}
}
result
}
pub fn snake_to_camel(s: &str) -> String {
let mut result = String::new();
let mut capitalize_next = false;
for c in s.chars() {
if c == '_' {
capitalize_next = true;
} else if capitalize_next {
result.push(c.to_uppercase().next().unwrap());
capitalize_next = false;
} else {
result.push(c);
}
}
result
}
pub fn capitalize(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().chain(chars).collect(),
}
}
pub fn uncapitalize(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_lowercase().chain(chars).collect(),
}
}
pub fn timestamp() -> i64 {
chrono::Utc::now().timestamp()
}
pub fn timestamp_millis() -> i64 {
chrono::Utc::now().timestamp_millis()
}
pub fn format_datetime(dt: chrono::DateTime<chrono::Utc>, format: &str) -> String {
dt.format(format).to_string()
}
pub fn parse_datetime(s: &str, format: &str) -> Option<chrono::DateTime<chrono::Utc>> {
chrono::NaiveDateTime::parse_from_str(s, format)
.ok()
.and_then(|dt| dt.and_local_timezone(chrono::Utc).single())
}
pub mod ip {
pub fn is_private(ip: &str) -> bool {
if let Ok(addr) = ip.parse::<std::net::IpAddr>() {
match addr {
std::net::IpAddr::V4(v4) => {
v4.is_private() || v4.is_loopback() || v4.is_link_local()
}
std::net::IpAddr::V6(v6) => v6.is_loopback(),
}
} else {
false
}
}
pub fn local_ip() -> Option<String> {
local_ip_address::local_ip().ok().map(|ip| ip.to_string())
}
}
pub mod string {
pub fn is_blank(s: &str) -> bool {
s.trim().is_empty()
}
pub fn is_not_blank(s: &str) -> bool {
!is_blank(s)
}
pub fn truncate(s: &str, max_len: usize) -> &str {
if s.len() <= max_len {
s
} else {
&s[..max_len]
}
}
pub fn strip_html(s: &str) -> String {
let re = regex::Regex::new(r"<[^>]*>").unwrap();
re.replace_all(s, "").to_string()
}
pub fn replace_all(s: &str, from: &str, to: &str) -> String {
s.replace(from, to)
}
pub fn capitalize(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().chain(chars).collect(),
}
}
pub fn uncapitalize(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_lowercase().chain(chars).collect(),
}
}
}
pub mod location {
use std::env;
use std::path::{Path, PathBuf};
pub fn get_executable_dir() -> Option<PathBuf> {
env::current_exe()
.ok()
.and_then(|exe| exe.parent().map(|p| p.to_path_buf()))
}
pub fn get_current_dir() -> Option<PathBuf> {
env::current_dir().ok()
}
pub fn get_project_root() -> Option<PathBuf> {
let mut current = env::current_dir().ok()?;
loop {
if current.join("Cargo.toml").exists() {
return Some(current);
}
if current.join("package.json").exists() {
return Some(current);
}
if current.join("dist").exists() {
return Some(current);
}
match current.parent() {
Some(parent) => current = parent.to_path_buf(),
None => break,
}
}
None
}
pub fn get_dist_path() -> Option<PathBuf> {
get_project_root().map(|root| root.join("dist"))
}
pub fn normalize_path(path: &Path) -> PathBuf {
path.canonicalize().unwrap_or_else(|_| path.to_path_buf())
}
pub fn join_path<P: AsRef<Path>>(base: &Path, parts: &[P]) -> PathBuf {
let mut result = base.to_path_buf();
for part in parts {
result = result.join(part);
}
result
}
pub fn relative_path(_from: &Path, _to: &Path) -> Option<PathBuf> {
None
}
pub fn path_exists(path: &Path) -> bool {
path.exists()
}
pub fn is_file(path: &Path) -> bool {
path.is_file()
}
pub fn is_dir(path: &Path) -> bool {
path.is_dir()
}
pub fn get_extension(path: &Path) -> Option<String> {
path.extension()
.and_then(|ext| ext.to_str())
.map(|s| s.to_string())
}
pub fn get_stem(path: &Path) -> Option<String> {
path.file_stem()
.and_then(|stem| stem.to_str())
.map(|s| s.to_string())
}
pub fn get_filename(path: &Path) -> Option<String> {
path.file_name()
.and_then(|name| name.to_str())
.map(|s| s.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_camel_to_snake() {
assert_eq!(camel_to_snake("userName"), "user_name");
assert_eq!(camel_to_snake("UserName"), "user_name");
assert_eq!(camel_to_snake("userId"), "user_id");
}
#[test]
fn test_snake_to_camel() {
assert_eq!(snake_to_camel("user_name"), "userName");
assert_eq!(snake_to_camel("user_id"), "userId");
}
#[test]
fn test_md5() {
assert_eq!(md5("hello"), "5d41402abc4b2a76b9719d911017c592");
}
#[test]
fn test_uuid() {
let id = uuid();
assert_eq!(id.len(), 36);
assert!(id.contains('-'));
}
}