use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FieldError {
pub field: String,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VldServerError {
pub message: String,
pub fields: Vec<FieldError>,
}
impl VldServerError {
pub fn validation(fields: Vec<FieldError>) -> Self {
let count = fields.len();
Self {
message: format!(
"Validation failed: {} field{} invalid",
count,
if count == 1 { "" } else { "s" }
),
fields,
}
}
pub fn internal(msg: impl Into<String>) -> Self {
Self {
message: msg.into(),
fields: Vec::new(),
}
}
pub fn field_error(&self, field: &str) -> Option<&str> {
self.fields
.iter()
.find(|f| f.field == field)
.map(|f| f.message.as_str())
}
pub fn field_errors(&self, field: &str) -> Vec<&str> {
self.fields
.iter()
.filter(|f| f.field == field)
.map(|f| f.message.as_str())
.collect()
}
pub fn has_field_error(&self, field: &str) -> bool {
self.fields.iter().any(|f| f.field == field)
}
pub fn error_fields(&self) -> Vec<&str> {
let mut names: Vec<&str> = self.fields.iter().map(|f| f.field.as_str()).collect();
names.dedup();
names
}
pub fn from_json(s: &str) -> Option<Self> {
serde_json::from_str(s).ok()
}
}
impl fmt::Display for VldServerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match serde_json::to_string(self) {
Ok(json) => write!(f, "{}", json),
Err(_) => write!(f, "{}", self.message),
}
}
}
impl std::error::Error for VldServerError {}
impl From<vld::error::VldError> for VldServerError {
fn from(error: vld::error::VldError) -> Self {
let fields: Vec<FieldError> = error
.issues
.iter()
.map(|issue| {
let field = issue
.path
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(".");
FieldError {
field,
message: issue.message.clone(),
}
})
.collect();
Self::validation(fields)
}
}
pub fn validate<S, T>(data: &T) -> Result<(), VldServerError>
where
S: vld::schema::VldParse,
T: serde::Serialize,
{
let json = serde_json::to_value(data)
.map_err(|e| VldServerError::internal(format!("Serialization error: {}", e)))?;
S::vld_parse_value(&json).map_err(VldServerError::from)?;
Ok(())
}
pub fn validate_value<S>(json: &serde_json::Value) -> Result<(), VldServerError>
where
S: vld::schema::VldParse,
{
S::vld_parse_value(json).map_err(VldServerError::from)?;
Ok(())
}
pub fn check_field<V, S>(value: &V, schema: &S) -> Option<String>
where
V: serde::Serialize,
S: vld::schema::VldSchema,
{
let json = serde_json::to_value(value).ok()?;
match schema.parse_value(&json) {
Ok(_) => None,
Err(e) => e.issues.first().map(|i| i.message.clone()),
}
}
pub fn check_field_all<V, S>(value: &V, schema: &S) -> Vec<String>
where
V: serde::Serialize,
S: vld::schema::VldSchema,
{
let json = match serde_json::to_value(value) {
Ok(j) => j,
Err(_) => return vec![],
};
match schema.parse_value(&json) {
Ok(_) => vec![],
Err(e) => e.issues.iter().map(|i| i.message.clone()).collect(),
}
}
pub fn check_all_fields<S, T>(data: &T) -> Vec<FieldError>
where
S: vld::schema::VldParse,
T: serde::Serialize,
{
let json = match serde_json::to_value(data) {
Ok(j) => j,
Err(_) => return vec![],
};
match S::vld_parse_value(&json) {
Ok(_) => vec![],
Err(e) => e
.issues
.iter()
.map(|i| FieldError {
field: i
.path
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join("."),
message: i.message.clone(),
})
.collect(),
}
}
#[macro_export]
macro_rules! validate_args {
($($field:ident => $schema:expr),* $(,)?) => {{
use ::vld::schema::VldSchema as _;
let mut __vld_field_errors: ::std::vec::Vec<$crate::FieldError> = ::std::vec::Vec::new();
$(
{
let __vld_json = ::vld::serde_json::to_value(&$field)
.unwrap_or(::vld::serde_json::Value::Null);
if let ::std::result::Result::Err(e) = ($schema).parse_value(&__vld_json) {
for issue in &e.issues {
__vld_field_errors.push($crate::FieldError {
field: ::std::string::String::from(stringify!($field)),
message: issue.message.clone(),
});
}
}
}
)*
if __vld_field_errors.is_empty() {
::std::result::Result::Ok::<(), $crate::VldServerError>(())
} else {
::std::result::Result::Err($crate::VldServerError::validation(__vld_field_errors))
}
}};
}
pub mod prelude {
pub use crate::{
check_all_fields, check_field, check_field_all, validate, validate_args, validate_value,
FieldError, VldServerError,
};
pub use vld::prelude::*;
}