use std::fmt::Debug;
use crate::error::Errors;
pub trait Validate {
type Context;
fn validate(&self, ctx: &Self::Context) -> Result<(), Errors>;
}
#[derive(Debug, Clone, Copy)]
pub struct Valid<T>(T);
impl<T: Validate> Valid<T> {
pub fn into_inner(self) -> T {
self.0
}
}
impl<T> std::ops::Deref for Valid<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Copy, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
pub struct Unvalidated<T>(T);
impl<T: Validate> Unvalidated<T> {
pub fn new(v: T) -> Self {
Self(v)
}
pub fn validate(self, ctx: &<T as Validate>::Context) -> Result<Valid<T>, Errors> {
self.0.validate(ctx)?;
Ok(Valid(self.0))
}
}
impl<T: Validate> From<T> for Unvalidated<T> {
fn from(value: T) -> Self {
Self(value)
}
}
impl<T: Debug> Debug for Unvalidated<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl<'a, T: ?Sized + Validate> Validate for &'a T {
type Context = T::Context;
fn validate(&self, ctx: &Self::Context) -> Result<(), Errors> {
<T as Validate>::validate(self, ctx)
}
}
impl<'a, T: ?Sized + Validate> Validate for &'a mut T {
type Context = T::Context;
fn validate(&self, ctx: &Self::Context) -> Result<(), Errors> {
<T as Validate>::validate(self, ctx)
}
}
impl<T: Validate> Validate for std::boxed::Box<T> {
type Context = T::Context;
fn validate(&self, ctx: &Self::Context) -> Result<(), Errors> {
<T as Validate>::validate(self, ctx)
}
}
impl<T: Validate> Validate for std::rc::Rc<T> {
type Context = T::Context;
fn validate(&self, ctx: &Self::Context) -> Result<(), Errors> {
<T as Validate>::validate(self, ctx)
}
}
impl<T: Validate> Validate for std::sync::Arc<T> {
type Context = T::Context;
fn validate(&self, ctx: &Self::Context) -> Result<(), Errors> {
<T as Validate>::validate(self, ctx)
}
}
macro_rules! impl_validate_list {
(<$T:ident $(, $Other:ident)*> $Container:ty) => {
impl<$T, $($Other),*> Validate for $Container
where
$T: Validate
{
type Context = T::Context;
fn validate(&self, ctx: &Self::Context) -> Result<(), Errors> {
let errors = Errors::list(|errors| {
for item in self.iter() {
errors.push(
<T as Validate>::validate(item, ctx)
.err()
.unwrap_or_else(Errors::empty),
)
}
});
if !errors.is_empty() {
return Err(errors);
}
Ok(())
}
}
};
}
impl_validate_list!(<T, S> std::collections::HashSet<T, S>);
impl_validate_list!(<T> std::collections::BTreeSet<T>);
impl_validate_list!(<T> std::collections::BinaryHeap<T>);
impl_validate_list!(<T> std::collections::LinkedList<T>);
impl_validate_list!(<T> std::collections::VecDeque<T>);
impl_validate_list!(<T> std::vec::Vec<T>);
impl_validate_list!(<T> [T]);
impl<T: Validate, const N: usize> Validate for [T; N] {
type Context = T::Context;
fn validate(&self, ctx: &Self::Context) -> Result<(), Errors> {
let errors = Errors::list(|errors| {
for item in self.iter() {
errors.push(
<T as Validate>::validate(item, ctx)
.err()
.unwrap_or_else(Errors::empty),
)
}
});
if !errors.is_empty() {
return Err(errors);
}
Ok(())
}
}
macro_rules! impl_validate_tuple {
($A:ident, $($T:ident),*) => {
impl<$A, $($T),*> Validate for ($A, $($T,)*)
where
$A : Validate,
$($T : Validate<Context=$A::Context>,)*
{
type Context = $A::Context;
#[allow(non_snake_case)]
fn validate(&self, ctx: &Self::Context) -> Result<(), Errors> {
let errors = Errors::list(|errors| {
let ($A, $($T,)*) = self;
errors.push(
<$A as Validate>::validate($A, ctx)
.err()
.unwrap_or_else(|| Errors::empty())
);
$(
errors.push(
<$T as Validate>::validate($T, ctx)
.err()
.unwrap_or_else(|| Errors::empty())
);
)*
});
if !errors.is_empty() {
return Err(errors);
}
Ok(())
}
}
}
}
impl_validate_tuple!(A,);
impl_validate_tuple!(A, B);
impl_validate_tuple!(A, B, C);
impl_validate_tuple!(A, B, C, D);
impl_validate_tuple!(A, B, C, D, E);
impl_validate_tuple!(A, B, C, D, E, F);
impl_validate_tuple!(A, B, C, D, E, F, G);
impl_validate_tuple!(A, B, C, D, E, F, G, H);
impl_validate_tuple!(A, B, C, D, E, F, G, H, I);
impl_validate_tuple!(A, B, C, D, E, F, G, H, I, J);
impl_validate_tuple!(A, B, C, D, E, F, G, H, I, J, K);
impl_validate_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
impl Validate for () {
type Context = ();
fn validate(&self, _: &Self::Context) -> Result<(), Errors> {
Ok(())
}
}
impl<K, V, S> Validate for std::collections::HashMap<K, V, S>
where
std::borrow::Cow<'static, str>: From<K>,
K: Clone,
V: Validate,
{
type Context = V::Context;
fn validate(&self, ctx: &Self::Context) -> Result<(), Errors> {
let errors = Errors::fields(|errors| {
for (key, value) in self.iter() {
errors.insert(
std::borrow::Cow::from(key.clone()),
<V as Validate>::validate(value, ctx)
.err()
.unwrap_or_else(Errors::empty),
)
}
});
if !errors.is_empty() {
return Err(errors);
}
Ok(())
}
}
impl<K, V> Validate for std::collections::BTreeMap<K, V>
where
std::borrow::Cow<'static, str>: From<K>,
K: Clone,
V: Validate,
{
type Context = V::Context;
fn validate(&self, ctx: &Self::Context) -> Result<(), Errors> {
let errors = Errors::fields(|errors| {
for (key, value) in self.iter() {
errors.insert(
std::borrow::Cow::from(key.clone()),
<V as Validate>::validate(value, ctx)
.err()
.unwrap_or_else(Errors::empty),
)
}
});
if !errors.is_empty() {
return Err(errors);
}
Ok(())
}
}
impl<T: Validate> Validate for Option<T> {
type Context = T::Context;
fn validate(&self, ctx: &Self::Context) -> Result<(), Errors> {
match self {
Some(value) => value.validate(ctx),
None => Ok(()),
}
}
}