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 seen = std::collections::HashSet::new();
self.fields
.iter()
.map(|f| f.field.as_str())
.filter(|f| seen.insert(*f))
.collect()
}
pub fn from_json(s: &str) -> Option<Self> {
serde_json::from_str(s).ok()
}
pub fn from_server_message(s: &str) -> Option<Self> {
let (start, end) = (s.find('{')?, s.rfind('}')?);
Self::from_json(s.get(start..=end)?)
}
}
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 {
Self::validation(field_errors(&error))
}
}
fn field_errors(error: &vld::error::VldError) -> Vec<FieldError> {
crate::flatten_issues(error)
.into_iter()
.map(|(field, message)| FieldError { field, message })
.collect()
}
fn ser_error(e: serde_json::Error) -> String {
format!("Serialization error: {}", e)
}
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(ser_error(e)))?;
validate_value::<S>(&json)
}
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,
{
check_field_all(value, schema).into_iter().next()
}
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(e) => return vec![ser_error(e)],
};
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(e) => {
return vec![FieldError {
field: String::new(),
message: ser_error(e),
}];
}
};
match S::vld_parse_value(&json) {
Ok(_) => vec![],
Err(e) => field_errors(&e),
}
}
#[doc(hidden)]
pub fn __arg_errors<V, S>(name: &str, value: &V, schema: &S, out: &mut Vec<FieldError>)
where
V: serde::Serialize,
S: vld::schema::VldSchema,
{
let json = match serde_json::to_value(value) {
Ok(j) => j,
Err(e) => {
out.push(FieldError {
field: name.to_string(),
message: ser_error(e),
});
return;
}
};
if let Err(e) = schema.parse_value(&json) {
for issue in &e.issues {
let sub = crate::format_path(&issue.path);
let field = if sub.is_empty() {
name.to_string()
} else if sub.starts_with('[') {
format!("{name}{sub}")
} else {
format!("{name}.{sub}")
};
out.push(FieldError {
field,
message: issue.message.clone(),
});
}
}
}
#[macro_export]
macro_rules! validate_args {
($($field:ident => $schema:expr),* $(,)?) => {{
let mut __vld_field_errors = ::std::vec::Vec::new();
$(
$crate::form::__arg_errors(stringify!($field), &$field, &($schema), &mut __vld_field_errors);
)*
if __vld_field_errors.is_empty() {
::std::result::Result::Ok::<(), $crate::form::VldServerError>(())
} else {
::std::result::Result::Err($crate::form::VldServerError::validation(__vld_field_errors))
}
}};
}
#[cfg(test)]
mod tests {
use super::*;
vld::schema! {
#[derive(Debug)]
struct Inner { email: String => vld::string().email() }
}
vld::schema! {
#[derive(Debug)]
struct Outer {
name: String => vld::string().min(2),
user: Inner => vld::nested(Inner::parse_value),
}
}
#[test]
fn paths_match_validate_args() {
let err: VldServerError =
Outer::parse_value(&serde_json::json!({"name": "A", "user": {"email": "bad"}}))
.unwrap_err()
.into();
assert!(err.field_error("name").is_some());
assert!(err.field_error("user.email").is_some());
let name = "A".to_string();
let err = crate::validate_args! { name => vld::string().min(2) }.unwrap_err();
assert!(err.field_error("name").is_some());
}
#[test]
fn error_fields_dedup_non_adjacent() {
let f = |n: &str| FieldError {
field: n.into(),
message: "x".into(),
};
let e = VldServerError::validation(vec![f("a"), f("b"), f("a")]);
assert_eq!(e.error_fields(), vec!["a", "b"]);
}
struct Bad;
impl serde::Serialize for Bad {
fn serialize<S: serde::Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
Err(serde::ser::Error::custom("nope"))
}
}
#[test]
fn serialization_failure_is_an_error() {
assert!(check_field(&Bad, &vld::string()).is_some());
assert!(!check_field_all(&Bad, &vld::string()).is_empty());
assert!(!check_all_fields::<Inner, _>(&Bad).is_empty());
}
}