use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ValidationError {
code: Cow<'static, str>,
message: String,
}
impl ValidationError {
pub fn new(code: &'static str, message: impl Into<String>) -> Self {
Self {
code: Cow::Borrowed(code),
message: message.into(),
}
}
pub fn custom_code(code: impl Into<Cow<'static, str>>, message: impl Into<String>) -> Self {
Self {
code: code.into(),
message: message.into(),
}
}
pub fn code(&self) -> &str {
&self.code
}
pub fn message(&self) -> &str {
&self.message
}
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}]: {}", self.code, self.message)
}
}
impl std::error::Error for ValidationError {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FieldError {
field: String,
errors: Vec<ValidationError>,
}
impl FieldError {
pub fn new(field: impl Into<String>, errors: Vec<ValidationError>) -> Self {
Self {
field: field.into(),
errors,
}
}
pub fn field(&self) -> &str {
&self.field
}
pub fn errors(&self) -> &[ValidationError] {
&self.errors
}
}
impl fmt::Display for FieldError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"field '{}' has {} error(s)",
self.field,
self.errors.len()
)
}
}
impl std::error::Error for FieldError {}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ValidationErrors {
fields: HashMap<String, Vec<ValidationError>>,
}
impl ValidationErrors {
pub fn new() -> Self {
Self {
fields: HashMap::new(),
}
}
pub fn add(&mut self, field: impl Into<String>, error: ValidationError) {
self.fields.entry(field.into()).or_default().push(error);
}
pub fn add_many(&mut self, field: impl Into<String>, errors: Vec<ValidationError>) {
if !errors.is_empty() {
self.fields.entry(field.into()).or_default().extend(errors);
}
}
pub fn merge(&mut self, other: ValidationErrors) {
for (field, errors) in other.fields {
self.add_many(field, errors);
}
}
pub fn prefix_fields(&mut self, prefix: &str) {
let old_fields = std::mem::take(&mut self.fields);
for (field, errors) in old_fields {
self.fields.insert(format!("{}{}", prefix, field), errors);
}
}
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
pub fn len(&self) -> usize {
self.fields.len()
}
pub fn has_field(&self, field: &str) -> bool {
self.fields.contains_key(field)
}
pub fn get(&self, field: &str) -> Option<&[ValidationError]> {
self.fields.get(field).map(|v| v.as_slice())
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &[ValidationError])> {
self.fields.iter().map(|(k, v)| (k.as_str(), v.as_slice()))
}
pub fn all_errors(&self) -> Vec<(&str, &ValidationError)> {
let mut all = Vec::new();
for (field, errs) in &self.fields {
for err in errs {
all.push((field.as_str(), err));
}
}
all
}
pub fn into_inner(self) -> HashMap<String, Vec<ValidationError>> {
self.fields
}
}
impl fmt::Display for ValidationErrors {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"Validation failed with {} invalid field(s):",
self.fields.len()
)?;
for (field, errors) in &self.fields {
for err in errors {
writeln!(f, " - [{}]: {}", field, err)?;
}
}
Ok(())
}
}
impl std::error::Error for ValidationErrors {}