use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationError {
pub field: String,
pub message: String,
}
impl ValidationError {
pub fn new(field: &str, message: &str) -> Self {
Self {
field: field.to_string(),
message: message.to_string(),
}
}
}
impl std::fmt::Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.field, self.message)
}
}
pub type ValidationResult<T> = std::result::Result<T, Vec<ValidationError>>;
pub trait Validator {
fn validate(&self, value: &str) -> bool;
fn message(&self) -> String;
fn try_message(&self) -> &'static str {
self.message().leak()
}
}
pub struct EmailValidator;
impl Validator for EmailValidator {
fn validate(&self, value: &str) -> bool {
value.contains('@') && value.contains('.')
}
fn message(&self) -> String {
"Invalid email format".to_string()
}
}
pub struct UrlValidator;
impl Validator for UrlValidator {
fn validate(&self, value: &str) -> bool {
value.starts_with("http://") || value.starts_with("https://")
}
fn message(&self) -> String {
"Invalid URL format".to_string()
}
}
pub struct LengthValidator {
min: usize,
max: usize,
}
impl LengthValidator {
pub fn new(min: usize, max: usize) -> Self {
Self { min, max }
}
pub fn min(min: usize) -> Self {
Self { min, max: usize::MAX }
}
pub fn max(max: usize) -> Self {
Self { min: 0, max }
}
}
impl Validator for LengthValidator {
fn validate(&self, value: &str) -> bool {
let len = value.len();
len >= self.min && len <= self.max
}
fn message(&self) -> String {
if self.max == usize::MAX {
format!("Length must be at least {}", self.min)
} else if self.min == 0 {
format!("Length must be at most {}", self.max)
} else {
format!("Length must be between {} and {}", self.min, self.max)
}
}
}
pub struct RegexValidator {
pattern: regex::Regex,
}
impl RegexValidator {
pub fn new(pattern: &str) -> Result<Self, regex::Error> {
Ok(Self {
pattern: regex::Regex::new(pattern)?,
})
}
}
impl Validator for RegexValidator {
fn validate(&self, value: &str) -> bool {
self.pattern.is_match(value)
}
fn message(&self) -> String {
"Invalid format".to_string()
}
}
pub struct RangeValidator {
min: Option<f64>,
max: Option<f64>,
}
impl RangeValidator {
pub fn new(min: Option<f64>, max: Option<f64>) -> Self {
Self { min, max }
}
}
impl Validator for RangeValidator {
fn validate(&self, value: &str) -> bool {
if let Ok(num) = value.parse::<f64>() {
let min_ok = self.min.map_or(true, |m| num >= m);
let max_ok = self.max.map_or(true, |m| num <= m);
min_ok && max_ok
} else {
false
}
}
fn message(&self) -> String {
"Value out of range".to_string()
}
}
pub struct UuidValidator;
impl Validator for UuidValidator {
fn validate(&self, value: &str) -> bool {
uuid::Uuid::parse_str(value).is_ok()
}
fn message(&self) -> String {
"Invalid UUID format".to_string()
}
}
pub struct ValidatorBuilder {
validators: Vec<Box<dyn Validator>>,
}
impl ValidatorBuilder {
pub fn new() -> Self {
Self {
validators: Vec::new(),
}
}
pub fn add<V: Validator + 'static>(mut self, validator: V) -> Self {
self.validators.push(Box::new(validator));
self
}
pub fn email(self) -> Self {
self.add(EmailValidator)
}
pub fn url(self) -> Self {
self.add(UrlValidator)
}
pub fn length(self, min: usize, max: usize) -> Self {
self.add(LengthValidator::new(min, max))
}
pub fn regex(self, pattern: &str) -> Result<Self, regex::Error> {
Ok(self.add(RegexValidator::new(pattern)?))
}
pub fn range(self, min: Option<f64>, max: Option<f64>) -> Self {
self.add(RangeValidator::new(min, max))
}
pub fn uuid(self) -> Self {
self.add(UuidValidator)
}
pub fn validate(&self, value: &str) -> ValidationResult<()> {
let mut errors = Vec::new();
for validator in &self.validators {
if !validator.validate(value) {
errors.push(ValidationError::new("field", &validator.message()));
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
impl Default for ValidatorBuilder {
fn default() -> Self {
Self::new()
}
}