Spaces:
Running
Running
| use once_cell::sync::Lazy; | |
| use regex::Regex; | |
| static PHONE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[6-9]\d{9}$").unwrap()); | |
| static EMAIL_REGEX: Lazy<Regex> = | |
| Lazy::new(|| Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap()); | |
| static PINCODE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[1-9]\d{5}$").unwrap()); | |
| pub fn validate_email(email: &str) -> Result<(), ValidationError> { | |
| if email.is_empty() { | |
| return Err(ValidationError::new("Email cannot be empty")); | |
| } | |
| if email.len() > 255 { | |
| return Err(ValidationError::new("Email too long")); | |
| } | |
| if !EMAIL_REGEX.is_match(email) { | |
| return Err(ValidationError::new("Invalid email format")); | |
| } | |
| Ok(()) | |
| } | |
| pub fn validate_pincode(pincode: &str) -> Result<(), ValidationError> { | |
| if pincode.is_empty() { | |
| return Err(ValidationError::new("Pincode cannot be empty")); | |
| } | |
| if !PINCODE_REGEX.is_match(pincode) { | |
| return Err(ValidationError::new( | |
| "Invalid Indian pincode format (6 digits starting with 1-9)", | |
| )); | |
| } | |
| Ok(()) | |
| } | |
| pub fn validate_price(price: f64) -> Result<(), ValidationError> { | |
| if price <= 0.0 { | |
| return Err(ValidationError::new("Price must be greater than 0")); | |
| } | |
| if price > 1_000_000.0 { | |
| return Err(ValidationError::new("Price exceeds maximum allowed value")); | |
| } | |
| if price.is_nan() || price.is_infinite() { | |
| return Err(ValidationError::new("Invalid price value")); | |
| } | |
| Ok(()) | |
| } | |
| pub fn validate_product_name(name: &str) -> Result<(), ValidationError> { | |
| if name.trim().is_empty() { | |
| return Err(ValidationError::new("Product name cannot be empty")); | |
| } | |
| if name.len() > 200 { | |
| return Err(ValidationError::new( | |
| "Product name too long (max 200 characters)", | |
| )); | |
| } | |
| Ok(()) | |
| } | |
| pub fn validate_weight(weight: f64) -> Result<(), ValidationError> { | |
| if weight < 0.0 { | |
| return Err(ValidationError::new("Weight cannot be negative")); | |
| } | |
| if weight > 100_000.0 { | |
| // 100kg limit for social checkout | |
| return Err(ValidationError::new( | |
| "Weight exceeds maximum allowed value (100kg)", | |
| )); | |
| } | |
| if weight.is_nan() || weight.is_infinite() { | |
| return Err(ValidationError::new("Invalid weight value")); | |
| } | |
| Ok(()) | |
| } | |
| pub fn sanitize_string(input: &str) -> String { | |
| input | |
| .trim() | |
| .chars() | |
| .filter(|c| !c.is_control()) | |
| .take(1000) | |
| .collect() | |
| } | |
| pub fn normalize_phone(phone: &str) -> Result<String, ValidationError> { | |
| if phone.is_empty() { | |
| return Err(ValidationError::new("Phone cannot be empty")); | |
| } | |
| let digits: String = phone.chars().filter(|c| c.is_ascii_digit()).collect(); | |
| let normalized = match digits.len() { | |
| 10 => digits, | |
| 11 if digits.starts_with('0') => digits[1..].to_string(), | |
| 12 if digits.starts_with("91") => digits[2..].to_string(), | |
| _ => return Err(ValidationError::new("Invalid Indian phone number format")), | |
| }; | |
| if !PHONE_REGEX.is_match(&normalized) { | |
| return Err(ValidationError::new("Invalid Indian phone number format")); | |
| } | |
| Ok(normalized) | |
| } | |
| pub fn validate_base64_payload(data: &str, max_bytes: usize) -> Result<Vec<u8>, ValidationError> { | |
| if data.is_empty() { | |
| return Err(ValidationError::new("Base64 payload cannot be empty")); | |
| } | |
| let raw_data = if let Some(pos) = data.find(',') { | |
| &data[pos + 1..] | |
| } else { | |
| data | |
| }; | |
| use base64::{engine::general_purpose, Engine as _}; | |
| match general_purpose::STANDARD.decode(raw_data) { | |
| Ok(bytes) => { | |
| if bytes.len() > max_bytes { | |
| return Err(ValidationError::new(&format!( | |
| "Payload exceeds maximum size of {} bytes", | |
| max_bytes | |
| ))); | |
| } | |
| Ok(bytes) | |
| } | |
| Err(_) => Err(ValidationError::new("Invalid Base64 encoding")), | |
| } | |
| } | |
| pub fn sanitize_filename(filename: &str) -> String { | |
| let sanitized: String = filename | |
| .replace("..", "") | |
| .chars() | |
| .filter(|c| c.is_alphanumeric() || *c == '.' || *c == '-' || *c == '_') | |
| .collect(); | |
| if sanitized.starts_with('.') || sanitized.is_empty() { | |
| return "unnamed".to_string(); | |
| } | |
| sanitized.chars().take(255).collect() | |
| } | |
| pub struct ValidationError { | |
| pub message: String, | |
| } | |
| impl ValidationError { | |
| pub fn new(msg: &str) -> Self { | |
| Self { | |
| message: msg.to_string(), | |
| } | |
| } | |
| } | |
| impl std::fmt::Display for ValidationError { | |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| write!(f, "{}", self.message) | |
| } | |
| } | |
| impl std::error::Error for ValidationError {} | |