pub trait StrExt {
fn is_blank(&self) -> bool;
fn to_camel(&self) -> String;
fn to_snake(&self) -> String;
fn truncate(&self, max: usize) -> String;
fn random(len: usize) -> String;
fn has_text(&self) -> bool { !self.is_blank() }
}
impl StrExt for str {
fn is_blank(&self) -> bool { self.trim().is_empty() }
fn to_camel(&self) -> String {
self.split('_')
.enumerate()
.map(|(i, w)| {
if i == 0 { w.to_lowercase() }
else { let mut c = w.chars(); c.next().map(|x| x.to_uppercase().chain(c).collect()).unwrap_or_default() }
})
.collect()
}
fn to_snake(&self) -> String {
let mut result = String::with_capacity(self.len() + 4);
for (i, ch) in self.chars().enumerate() {
if ch.is_uppercase() && i > 0 {
result.push('_');
}
result.push(ch.to_ascii_lowercase());
}
result
}
fn truncate(&self, max: usize) -> String {
if self.len() <= max { self.to_string() }
else { format!("{}...", &self[..max]) }
}
fn random(len: usize) -> String {
use rand::Rng;
const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789";
let mut rng = rand::thread_rng();
(0..len).map(|_| CHARSET[rng.gen_range(0..CHARSET.len())] as char).collect()
}
}
pub fn sanitize_filename(filename: &str) -> String {
use regex::Regex;
let re = Regex::new(r"[^a-zA-Z0-9.\-_]").unwrap();
re.replace_all(filename, "_").to_string()
}
pub fn parse_json_value(value: &str) -> Result<serde_json::Value, serde_json::Error> {
serde_json::from_str(value)
}
pub fn format_file_size(bytes: u64) -> String {
const UNITS: [&str; 6] = ["B", "KB", "MB", "GB", "TB", "PB"];
if bytes == 0 {
return "0 B".to_string();
}
let i = (bytes as f64).log(1024.0).floor() as i32;
let size = bytes as f64 / 1024_f64.powi(i);
let unit = UNITS.get(i as usize).unwrap_or(&"B");
format!("{:.2} {}", size, unit)
}
pub fn clean_string_param(s: &str) -> String {
s.trim().to_string()
}
pub fn clean_email(email: &str) -> String {
email.trim().to_lowercase()
}
pub fn clean_password(password: &str) -> String {
password.trim().to_string()
}
pub struct InputCleaner;
impl InputCleaner {
pub fn clean_register_input(
email: &str,
password: &str,
nickname: &str,
) -> (String, String, String) {
let email = clean_email(email);
let password = clean_password(password);
let nickname = clean_string_param(nickname);
(email, password, nickname)
}
pub fn clean_login_input(email: &str, password: &str) -> (String, String) {
let email = clean_email(email);
let password = clean_password(password);
(email, password)
}
}
pub fn generate_invite_code() -> String {
use rand::{Rng, distributions::Alphanumeric};
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(12)
.map(char::from)
.collect()
}
pub fn generate_random_digits(n: usize) -> String {
if n == 0 {
return String::new();
}
use rand::Rng;
let mut rng = rand::thread_rng();
(0..n)
.map(|_| (rng.gen_range(1..=9) + b'0') as char)
.collect()
}
pub fn generate_random_alphanum(length: usize) -> String {
const CHARSET: &[u8] = b"ABCDEFGHJKMNPQRSTUVWXYZ\
abcdefghjkmnpqrstuvwxyz\
123456789";
use rand::Rng;
let mut rng = rand::thread_rng();
let mut result = String::with_capacity(length);
for _ in 0..length {
let idx = rng.gen_range(0..CHARSET.len());
result.push(CHARSET[idx] as char);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_camel() { assert_eq!("user_name".to_camel(), "userName"); }
#[test]
fn test_snake() { assert_eq!("UserName".to_snake(), "user_name"); }
#[test]
fn test_blank() { assert!(" ".is_blank()); assert!(!"abc".is_blank()); }
}