mod path;
use core::fmt;
pub use path::Path;
#[cfg(feature = "alloc")]
use crate::alloc::{borrow::Cow, vec::Vec};
use crate::options::rule;
#[derive(Debug, thiserror::Error, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Alphanumeric(#[from] rule::alphanumeric::Error),
#[error(transparent)]
Ascii(#[from] rule::ascii::Error),
#[error(transparent)]
Addr(#[from] rule::addr::Error),
#[error(transparent)]
Lowercase(#[from] rule::lowercase::Error),
#[error(transparent)]
Uppercase(#[from] rule::uppercase::Error),
#[error(transparent)]
Contains(#[from] rule::contains::Error),
#[error(transparent)]
Prefix(#[from] rule::prefix::Error),
#[error(transparent)]
Suffix(#[from] rule::suffix::Error),
#[error(transparent)]
Equals(#[from] rule::equals::Error),
#[cfg(feature = "email")]
#[error(transparent)]
Email(#[from] rule::email::Error),
#[cfg(feature = "url")]
#[error(transparent)]
Url(#[from] rule::url::Error),
#[error(transparent)]
Length(#[from] rule::length::Error),
#[error(transparent)]
Range(#[from] rule::range::Error),
#[cfg(feature = "semver")]
#[error(transparent)]
Semver(#[from] rule::semver::Error),
#[cfg(feature = "regex")]
#[error(transparent)]
Regex(#[from] rule::regex::Error),
#[error(transparent)]
Required(#[from] rule::required::Error),
#[cfg(feature = "uuid")]
#[error(transparent)]
Uuid(#[from] rule::uuid::Error),
#[cfg(feature = "credit_card")]
#[error(transparent)]
CreditCard(#[from] rule::credit_card::Error),
#[cfg(any(feature = "jiff", feature = "chrono"))]
#[error(transparent)]
Time(#[from] rule::time::Error),
#[error("{code}")]
#[cfg_attr(feature = "serde", serde(skip_serializing))]
Custom {
code: &'static str,
#[cfg(feature = "alloc")]
message: Option<Cow<'static, str>>,
#[cfg(not(feature = "alloc"))]
message: Option<&'static str>,
},
}
#[allow(dead_code)]
impl Error {
#[must_use]
pub fn new(code: &'static str) -> Self {
Self::Custom {
code,
message: None,
}
}
#[must_use]
pub fn is_custom(&self) -> bool {
matches!(self, Self::Custom { .. })
}
#[cfg(feature = "alloc")]
pub fn with_message(code: &'static str, message: impl Into<Cow<'static, str>>) -> Self {
Self::Custom {
code,
message: Some(message.into()),
}
}
#[cfg(not(feature = "alloc"))]
pub fn with_message(code: &'static str, message: &'static str) -> Self {
Self::Custom {
code,
message: Some(message),
}
}
#[must_use]
pub(crate) fn code(&self) -> &'static str {
match self {
Self::Alphanumeric(error) => error.code(),
Self::Ascii(error) => error.code(),
Self::Addr(error) => error.code(),
Self::Lowercase(error) => error.code(),
Self::Uppercase(error) => error.code(),
Self::Contains(error) => error.code(),
Self::Prefix(error) => error.code(),
Self::Suffix(error) => error.code(),
Self::Equals(error) => error.code(),
#[cfg(feature = "email")]
Self::Email(error) => error.code(),
#[cfg(feature = "url")]
Self::Url(error) => error.code(),
Self::Length(error) => error.code(),
Self::Range(error) => error.code(),
#[cfg(feature = "semver")]
Self::Semver(error) => error.code(),
#[cfg(feature = "regex")]
Self::Regex(error) => error.code(),
Self::Required(error) => error.code(),
#[cfg(feature = "uuid")]
Self::Uuid(error) => error.code(),
#[cfg(feature = "credit_card")]
Self::CreditCard(error) => error.code(),
#[cfg(any(feature = "jiff", feature = "chrono"))]
Self::Time(error) => error.code(),
Self::Custom { code, .. } => code,
}
}
#[cfg(feature = "alloc")]
pub(crate) fn message(&self) -> Option<Cow<str>> {
Some(match self {
Self::Alphanumeric(error) => error.message().into(),
Self::Ascii(error) => error.message().into(),
Self::Addr(error) => error.message().into(),
Self::Lowercase(error) => error.message(),
Self::Uppercase(error) => error.message(),
Self::Contains(error) => error.message(),
Self::Prefix(error) => error.message(),
Self::Suffix(error) => error.message(),
Self::Equals(error) => error.message(),
#[cfg(feature = "email")]
Self::Email(error) => error.message().into(),
#[cfg(feature = "url")]
Self::Url(error) => error.message().into(),
Self::Length(error) => error.message(),
Self::Range(error) => error.message().into(),
#[cfg(feature = "semver")]
Self::Semver(error) => error.message().into(),
#[cfg(feature = "regex")]
Self::Regex(error) => error.message(),
Self::Required(error) => error.message().into(),
#[cfg(feature = "uuid")]
Self::Uuid(error) => error.message().into(),
#[cfg(feature = "credit_card")]
Self::CreditCard(error) => error.message().into(),
#[cfg(any(feature = "jiff", feature = "chrono"))]
Self::Time(error) => error.message().into(),
#[cfg(feature = "alloc")]
Self::Custom { message, .. } => return message.as_deref().map(Cow::Borrowed),
#[cfg(not(feature = "alloc"))]
Self::Custom { message, .. } => return *message,
})
}
}
#[derive(Debug, Default)]
pub struct Report {
#[cfg(feature = "alloc")]
errors: Vec<(Path, Error)>,
#[cfg(not(feature = "alloc"))]
errors: [Option<(Path, Error)>; 1],
#[cfg(not(feature = "alloc"))]
len: usize,
}
impl fmt::Display for Report {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Report({} errors)", self.errors.len())
}
}
impl core::error::Error for Report {}
#[cfg(feature = "alloc")]
impl Report {
pub fn push(&mut self, path: Path, error: Error) {
self.errors.push((path, error));
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.errors.len()
}
pub fn clear(&mut self) {
self.errors.clear();
}
pub fn extend(&mut self, other: Self) {
self.errors.extend(other.errors);
}
}
#[cfg(not(feature = "alloc"))]
impl Report {
pub fn push(&mut self, path: Path, error: Error) {
if self.len < self.errors.len() {
self.errors[self.len] = Some((path, error));
}
self.len += 1;
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
pub fn clear(&mut self) {
for i in 0..self.len.min(self.errors.len()) {
self.errors[i] = None;
}
self.len = 0;
}
pub fn extend(&mut self, other: Self) {
for error in other.errors {
if self.len < self.errors.len() {
self.errors[self.len] = error;
}
self.len += 1;
}
}
}
#[cfg(feature = "serde")]
mod ser {
use super::*;
#[derive(serde::Serialize)]
struct Inner<'d> {
path: &'d Path,
code: &'static str,
message: Option<Cow<'d, str>>,
detail: &'d Error,
}
#[cfg(feature = "alloc")]
impl serde::Serialize for Report {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
let mut seq = serializer.serialize_seq(Some(self.errors.len()))?;
for (path, error) in &self.errors {
let detail = Inner {
path,
code: error.code(),
message: error.message(),
detail: error,
};
seq.serialize_element(&detail)?;
}
seq.end()
}
}
#[cfg(not(feature = "alloc"))]
impl serde::Serialize for Report {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
let mut seq = serializer.serialize_seq(Some(self.len))?;
for i in 0..self.len {
if let Some((path, error)) = &self.errors[i] {
let detail = Inner {
path,
code: error.code(),
message: error.message(),
detail: error,
};
seq.serialize_element(&detail)?;
}
}
}
}
}