use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidationError {
pub message: String,
pub path: Option<String>,
pub value: Option<serde_json::Value>,
}
impl ValidationError {
pub fn new(
message: impl Into<String>,
path: Option<String>,
value: Option<serde_json::Value>,
) -> Self {
Self {
message: message.into(),
path,
value,
}
}
pub fn with_path(message: impl Into<String>, path: String) -> Self {
Self {
message: message.into(),
path: Some(path),
value: None,
}
}
pub fn without_path(message: impl Into<String>) -> Self {
Self {
message: message.into(),
path: None,
value: None,
}
}
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.path {
Some(path) => write!(f, "{}: {}", path, self.message),
None => write!(f, "{}", self.message),
}
}
}
impl std::error::Error for ValidationError {}
#[derive(Debug, Clone, PartialEq)]
pub struct ValidationResult {
pub valid: bool,
pub errors: Vec<ValidationError>,
pub data: Option<serde_json::Value>,
}
impl ValidationResult {
pub fn success(data: serde_json::Value) -> Self {
Self {
valid: true,
errors: Vec::new(),
data: Some(data),
}
}
pub fn failure(errors: Vec<ValidationError>) -> Self {
Self {
valid: false,
errors,
data: None,
}
}
pub fn from_error(error: ValidationError) -> Self {
Self {
valid: false,
errors: vec![error],
data: None,
}
}
pub fn from_messages(messages: Vec<String>) -> Self {
Self {
valid: false,
errors: messages
.into_iter()
.map(ValidationError::without_path)
.collect(),
data: None,
}
}
pub fn is_valid(&self) -> bool {
self.valid
}
pub fn data(&self) -> Option<&serde_json::Value> {
self.data.as_ref()
}
pub fn error_messages(&self) -> Vec<String> {
self.errors.iter().map(|e| e.to_string()).collect()
}
pub fn merge(results: Vec<ValidationResult>) -> Self {
let mut all_errors = Vec::new();
let mut all_valid = true;
let mut final_data = None;
let any_results = !results.is_empty();
for result in results {
if !result.valid {
all_valid = false;
all_errors.extend(result.errors);
} else if final_data.is_none() {
final_data = result.data;
}
}
Self {
valid: all_valid,
errors: all_errors,
data: if all_valid && any_results {
final_data
} else {
None
},
}
}
pub fn into_result(self) -> Result<serde_json::Value, Vec<ValidationError>> {
if self.valid {
Ok(self.data.unwrap_or(serde_json::Value::Null))
} else {
Err(self.errors)
}
}
}
impl From<ValidationError> for ValidationResult {
fn from(error: ValidationError) -> Self {
Self::from_error(error)
}
}
impl From<Vec<ValidationError>> for ValidationResult {
fn from(errors: Vec<ValidationError>) -> Self {
Self::failure(errors)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validation_error_display_without_path() {
let error = ValidationError::without_path("Invalid type");
assert_eq!(error.to_string(), "Invalid type");
assert!(error.path.is_none());
}
#[test]
fn test_validation_error_display_with_path() {
let error = ValidationError::with_path("Invalid type", "user.name".to_string());
assert_eq!(error.to_string(), "user.name: Invalid type");
assert_eq!(error.path, Some("user.name".to_string()));
}
#[test]
fn test_validation_result_success() {
let data = serde_json::json!({"name": "Alice"});
let result = ValidationResult::success(data.clone());
assert!(result.is_valid());
assert_eq!(result.data(), Some(&data));
assert!(result.errors.is_empty());
}
#[test]
fn test_validation_result_failure() {
let errors = vec![
ValidationError::with_path("Missing field", "user.name".to_string()),
ValidationError::with_path("Invalid type", "user.age".to_string()),
];
let result = ValidationResult::failure(errors.clone());
assert!(!result.is_valid());
assert!(result.data.is_none());
assert_eq!(result.errors.len(), 2);
}
#[test]
fn test_validation_result_from_error() {
let error = ValidationError::without_path("Test error");
let result = ValidationResult::from_error(error.clone());
assert!(!result.is_valid());
assert_eq!(result.errors.len(), 1);
let first_error = &result.errors[0];
assert!(first_error.path.is_none());
}
#[test]
fn test_validation_result_from_messages() {
let messages = vec!["Error 1".to_string(), "Error 2".to_string()];
let result = ValidationResult::from_messages(messages);
assert!(!result.is_valid());
assert_eq!(result.errors.len(), 2);
assert!(result.errors[0].path.is_none());
}
#[test]
fn test_validation_result_merge_all_success() {
let result1 = ValidationResult::success(serde_json::json!(1));
let result2 = ValidationResult::success(serde_json::json!(2));
let merged = ValidationResult::merge(vec![result1, result2]);
assert!(merged.is_valid());
}
#[test]
fn test_validation_result_merge_one_failure() {
let result1 = ValidationResult::success(serde_json::json!(1));
let error = ValidationError::without_path("Test error");
let result2 = ValidationResult::from_error(error);
let merged = ValidationResult::merge(vec![result1, result2]);
assert!(!merged.is_valid());
assert_eq!(merged.errors.len(), 1);
}
#[test]
fn test_validation_result_into_result_success() {
let data = serde_json::json!({"test": 123});
let result = ValidationResult::success(data.clone());
assert_eq!(result.into_result(), Ok(data));
}
#[test]
fn test_validation_result_into_result_failure() {
let errors = vec![ValidationError::without_path("Test error")];
let result = ValidationResult::failure(errors.clone());
assert_eq!(result.into_result(), Err(errors));
}
#[test]
fn test_error_messages() {
let errors = vec![
ValidationError::with_path("Error 1", "path1".to_string()),
ValidationError::without_path("Error 2"),
];
let result = ValidationResult::failure(errors);
let messages = result.error_messages();
assert_eq!(messages.len(), 2);
assert_eq!(messages[0], "path1: Error 1");
assert_eq!(messages[1], "Error 2");
}
}