use serde::Serialize;
use std::fmt;
#[derive(Debug, Clone, Serialize)]
pub struct ValidationError {
pub field: String,
pub message: String,
}
impl ValidationError {
#[must_use]
pub fn new(field: impl Into<String>, message: impl Into<String>) -> Self {
Self {
field: field.into(),
message: message.into(),
}
}
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.field, self.message)
}
}
impl std::error::Error for ValidationError {}
#[derive(Debug, Clone, Serialize)]
pub struct ValidationErrors {
errors: Vec<ValidationError>,
}
impl ValidationErrors {
#[must_use]
pub fn new() -> Self {
Self { errors: Vec::new() }
}
pub fn add(&mut self, error: ValidationError) {
self.errors.push(error);
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.errors.len()
}
#[must_use]
pub fn errors(&self) -> &[ValidationError] {
&self.errors
}
pub fn result(self) -> Result<(), Self> {
if self.is_empty() { Ok(()) } else { Err(self) }
}
}
impl Default for ValidationErrors {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for ValidationErrors {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Validation failed with {} error(s):", self.errors.len())?;
for error in &self.errors {
write!(f, "\n - {error}")?;
}
Ok(())
}
}
impl std::error::Error for ValidationErrors {}
pub trait Validatable {
fn validate(&self) -> Result<(), ValidationError>;
}
pub mod validators {
use super::ValidationError;
pub fn validate_length(
field: &str,
value: &str,
min: Option<usize>,
max: Option<usize>,
) -> Result<(), ValidationError> {
let len = value.len();
if let Some(min_len) = min
&& len < min_len
{
return Err(ValidationError::new(
field,
format!("Must be at least {min_len} characters"),
));
}
if let Some(max_len) = max
&& len > max_len
{
return Err(ValidationError::new(
field,
format!("Must be at most {max_len} characters"),
));
}
Ok(())
}
pub fn validate_range<T: PartialOrd + fmt::Display>(
field: &str,
value: T,
min: Option<T>,
max: Option<T>,
) -> Result<(), ValidationError> {
if let Some(min_val) = min
&& value < min_val
{
return Err(ValidationError::new(
field,
format!("Must be at least {min_val}"),
));
}
if let Some(max_val) = max
&& value > max_val
{
return Err(ValidationError::new(
field,
format!("Must be at most {max_val}"),
));
}
Ok(())
}
pub fn validate_email(field: &str, value: &str) -> Result<(), ValidationError> {
if !value.contains('@') || !value.contains('.') {
return Err(ValidationError::new(field, "Invalid email format"));
}
if value.len() > 255 {
return Err(ValidationError::new(
field,
"Email must be at most 255 characters",
));
}
Ok(())
}
pub fn validate_required(field: &str, value: &str) -> Result<(), ValidationError> {
if value.trim().is_empty() {
return Err(ValidationError::new(field, "This field is required"));
}
Ok(())
}
use std::fmt;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validation_error_creation() {
let err = ValidationError::new("email", "Invalid email");
assert_eq!(err.field, "email");
assert_eq!(err.message, "Invalid email");
}
#[test]
fn test_validation_errors_collection() {
let mut errors = ValidationErrors::new();
assert!(errors.is_empty());
errors.add(ValidationError::new("field1", "error1"));
assert_eq!(errors.len(), 1);
errors.add(ValidationError::new("field2", "error2"));
assert_eq!(errors.len(), 2);
assert!(errors.result().is_err());
}
#[test]
fn test_validate_length() {
use validators::validate_length;
assert!(validate_length("name", "ab", Some(3), None).is_err());
assert!(validate_length("name", "abcdef", None, Some(5)).is_err());
assert!(validate_length("name", "abc", Some(3), Some(5)).is_ok());
}
#[test]
fn test_validate_range() {
use validators::validate_range;
assert!(validate_range("age", 5, Some(10), None).is_err());
assert!(validate_range("age", 150, None, Some(120)).is_err());
assert!(validate_range("age", 25, Some(0), Some(120)).is_ok());
}
#[test]
fn test_validate_email() {
use validators::validate_email;
assert!(validate_email("email", "invalid").is_err());
assert!(validate_email("email", "test@example.com").is_ok());
}
#[test]
fn test_validate_required() {
use validators::validate_required;
assert!(validate_required("name", "").is_err());
assert!(validate_required("name", " ").is_err());
assert!(validate_required("name", "John").is_ok());
}
}