use std::{
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
fmt,
fmt::Formatter,
ops,
sync::Arc,
};
use regex::Regex;
use serde::de;
use crate::{
ErrorWithOrigin,
pat::{LazyRegex, RawStr},
};
#[doc(hidden)] pub mod _private;
pub trait Validate<T: ?Sized>: 'static + Send + Sync {
fn describe(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result;
fn validate(&self, target: &T) -> Result<(), ErrorWithOrigin>;
}
impl<T: 'static + ?Sized> fmt::Debug for dyn Validate<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_tuple("Validate")
.field(&self.to_string())
.finish()
}
}
impl<T: 'static + ?Sized> fmt::Display for dyn Validate<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.describe(formatter)
}
}
impl<T: ?Sized, V: Validate<T> + ?Sized> Validate<T> for &'static V {
fn describe(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).describe(formatter)
}
fn validate(&self, target: &T) -> Result<(), ErrorWithOrigin> {
(**self).validate(target)
}
}
macro_rules! impl_validate_for_range {
($range:path) => {
impl<T> Validate<T> for $range
where
T: 'static + Send + Sync + PartialOrd + fmt::Debug,
{
fn describe(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "must be in range {self:?}")
}
fn validate(&self, target: &T) -> Result<(), ErrorWithOrigin> {
if !self.contains(target) {
let err = de::Error::invalid_value(
de::Unexpected::Other(&format!("{target:?}")),
&format!("value in range {self:?}").as_str(),
);
return Err(ErrorWithOrigin::json(err, Arc::default()));
}
Ok(())
}
}
};
}
impl_validate_for_range!(ops::Range<T>);
impl_validate_for_range!(ops::RangeInclusive<T>);
impl_validate_for_range!(ops::RangeTo<T>);
impl_validate_for_range!(ops::RangeToInclusive<T>);
impl_validate_for_range!(ops::RangeFrom<T>);
#[derive(Debug)]
pub struct NotEmpty;
macro_rules! impl_not_empty_validation {
($ty:ident$(<$($arg:ident),+>)?) => {
impl$(<$($arg,)+>)? Validate<$ty$(<$($arg,)+>)?> for NotEmpty {
fn describe(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("must not be empty")
}
fn validate(&self, target: &$ty$(<$($arg,)+>)?) -> Result<(), ErrorWithOrigin> {
if target.is_empty() {
return Err(de::Error::custom("value is empty"));
}
Ok(())
}
}
};
}
impl_not_empty_validation!(String);
impl_not_empty_validation!(Vec<T>);
impl_not_empty_validation!(HashMap<K, V, S>);
impl_not_empty_validation!(BTreeMap<K, V>);
impl_not_empty_validation!(HashSet<K, S>);
impl_not_empty_validation!(BTreeSet<K>);
impl<T> Validate<String> for LazyRegex<T>
where
T: ops::Deref<Target = Regex> + Send + Sync + 'static,
{
fn describe(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(formatter, "must match Regex({})", RawStr(self.0.as_str()))
}
fn validate(&self, target: &String) -> Result<(), ErrorWithOrigin> {
let target = target.as_ref();
if self.0.is_match(target) {
Ok(())
} else {
Err(de::Error::custom(format_args!(
"value does not match Regex({})",
RawStr(self.0.as_str())
)))
}
}
}