#![warn(rust_2018_idioms)]
#![warn(rust_2021_compatibility)]
#![warn(missing_debug_implementations)]
#![warn(unreachable_pub)]
#![warn(unsafe_code)]
#![warn(clippy::pedantic)]
#![allow(clippy::default_trait_access)]
#![warn(rustdoc::broken_intra_doc_links)]
#![cfg_attr(not(feature = "std"), no_std)]
use core::{any::Any, fmt::Debug, ops::Deref};
pub mod context;
use self::context::Context;
mod smallvec;
mod util;
use self::util::UnitResult;
pub mod prelude {
pub use super::{
context::Context as ValidationContext, IntoValidated, Invalidity, IsValid, Validate,
Validated, ValidatedFrom, ValidatedResult, ValidationResult,
};
}
pub type ValidationResult<V> = UnitResult<Context<V>>;
pub trait Invalidity: Any + Debug {}
impl<V> Invalidity for V where V: Any + Debug {}
pub trait Validate {
type Invalidity: Invalidity;
fn validate(&self) -> ValidationResult<Self::Invalidity>;
}
pub trait IsValid {
fn is_valid(&self) -> bool;
}
impl<T> IsValid for T
where
T: Validate + ?Sized,
{
fn is_valid(&self) -> bool {
self.validate().is_ok()
}
}
impl<'a, V> Validate for &'a V
where
V: Validate + ?Sized,
{
type Invalidity = V::Invalidity;
fn validate(&self) -> ValidationResult<Self::Invalidity> {
(*self).validate()
}
}
impl<V> Validate for Option<V>
where
V: Validate,
{
type Invalidity = V::Invalidity;
fn validate(&self) -> ValidationResult<Self::Invalidity> {
if let Some(ref some) = self {
some.validate()
} else {
Ok(())
}
}
}
impl<V> Validate for [V]
where
V: Validate,
{
type Invalidity = V::Invalidity;
fn validate(&self) -> ValidationResult<Self::Invalidity> {
self.iter().fold(Context::new(), Context::validate).into()
}
}
#[cfg(feature = "std")]
impl<V> Validate for Vec<V>
where
V: Validate,
{
type Invalidity = V::Invalidity;
fn validate(&self) -> ValidationResult<Self::Invalidity> {
self.as_slice().validate()
}
}
#[cfg(feature = "std")]
impl<'a, V> Validate for std::borrow::Cow<'a, V>
where
V: Validate + ToOwned + 'a + ?Sized,
{
type Invalidity = V::Invalidity;
fn validate(&self) -> ValidationResult<Self::Invalidity> {
self.as_ref().validate()
}
}
#[derive(Debug, Clone, Copy)]
pub struct Validated<T>(T);
impl<T> Validated<T> {
pub fn into(self) -> T {
self.0
}
#[inline]
pub const fn as_ref(&self) -> Validated<&T> {
Validated(&self.0)
}
}
impl<T> AsRef<T> for Validated<T> {
fn as_ref(&self) -> &T {
self
}
}
impl<T> Deref for Validated<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub type ValidatedResult<T> =
core::result::Result<Validated<T>, (T, Context<<T as Validate>::Invalidity>)>;
pub trait ValidatedFrom<T>: Validate + Sized {
fn validated_from(from: T) -> ValidatedResult<Self>;
}
impl<T> ValidatedFrom<T> for T
where
T: Validate,
{
fn validated_from(from: T) -> ValidatedResult<Self> {
if let Err(err) = from.validate() {
Err((from, err))
} else {
Ok(Validated(from))
}
}
}
pub trait IntoValidated<V: Validate> {
fn into_validated(self) -> ValidatedResult<V>;
}
impl<T, V> IntoValidated<V> for T
where
V: ValidatedFrom<T>,
{
fn into_validated(self) -> ValidatedResult<V> {
V::validated_from(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
struct AlwaysValid;
impl Validate for AlwaysValid {
type Invalidity = ();
fn validate(&self) -> ValidationResult<Self::Invalidity> {
Context::new().into()
}
}
struct AlwaysInvalid;
impl Validate for AlwaysInvalid {
type Invalidity = ();
fn validate(&self) -> ValidationResult<Self::Invalidity> {
Context::new().invalidate(()).into()
}
}
struct Dummy {
is_valid: bool,
}
impl Dummy {
fn valid() -> Self {
Self { is_valid: true }
}
fn invalid() -> Self {
Self { is_valid: false }
}
}
impl Validate for Dummy {
type Invalidity = ();
fn validate(&self) -> ValidationResult<Self::Invalidity> {
Context::new().invalidate_if(!self.is_valid, ()).into()
}
}
#[test]
fn validate() {
assert!(AlwaysValid.validate().is_ok());
assert!(AlwaysInvalid.validate().is_err());
}
#[test]
fn validate_option_none() {
assert!((None as Option<AlwaysValid>).validate().is_ok());
assert!((None as Option<AlwaysInvalid>).validate().is_ok());
}
#[test]
fn validate_option_ref_none() {
assert!((None as Option<&AlwaysValid>).validate().is_ok());
assert!((None as Option<&AlwaysInvalid>).validate().is_ok());
}
#[test]
fn validate_option_some() {
assert!(Some(AlwaysValid).validate().is_ok());
assert!(Some(AlwaysInvalid).validate().is_err());
}
#[test]
fn validate_option_ref_some() {
assert!(Some(&AlwaysValid).validate().is_ok());
assert!(Some(&AlwaysInvalid).validate().is_err());
}
#[test]
fn validate_slices() {
assert!([Dummy::valid(), Dummy::valid()].validate().is_ok());
assert_eq!(
1,
([Dummy::valid(), Dummy::invalid()].as_slice())
.validate()
.unwrap_err()
.into_iter()
.count()
);
assert_eq!(
1,
[Dummy::invalid(), Dummy::valid()]
.validate()
.unwrap_err()
.into_iter()
.count()
);
assert_eq!(
2,
([Dummy::invalid(), Dummy::invalid()])
.validate()
.unwrap_err()
.into_iter()
.count()
);
}
#[test]
#[cfg(feature = "std")]
fn validate_borrowed_vec() {
let vec = vec![Dummy::valid(), Dummy::valid()];
let borrowed_vec = &vec;
assert!(borrowed_vec.validate().is_ok());
}
#[test]
fn validate_slices_ref() {
let valid = Dummy::valid();
let invalid = Dummy::invalid();
assert!([&valid, &valid].validate().is_ok());
assert_eq!(
1,
[&valid, &invalid]
.validate()
.unwrap_err()
.into_iter()
.count()
);
assert_eq!(
1,
[&invalid, &valid]
.validate()
.unwrap_err()
.into_iter()
.count()
);
assert_eq!(
2,
[&invalid, &invalid]
.validate()
.unwrap_err()
.into_iter()
.count()
);
}
#[test]
fn validated_from() {
assert!(AlwaysValid::validated_from(AlwaysValid).is_ok());
assert!(AlwaysInvalid::validated_from(AlwaysInvalid).is_err());
}
#[test]
fn into_validated() {
assert!(IntoValidated::<AlwaysValid>::into_validated(AlwaysValid).is_ok());
assert!(IntoValidated::<AlwaysInvalid>::into_validated(AlwaysInvalid).is_err());
}
#[test]
fn is_valid() {
assert!(AlwaysValid.is_valid());
assert!(!AlwaysInvalid.is_valid());
}
}