use std::{fmt::Display, marker::PhantomData};
use thiserror::Error;
use crate::{fast_rule, vstr::*};
#[derive(Debug, Error)]
pub struct StringSizeOutOfRangeError {
pub min: usize,
pub max: usize,
pub actual: usize,
}
impl Display for StringSizeOutOfRangeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "string size range falsified: ")?;
if self.min != 0 {
write!(f, "{} <= ", self.min)?;
}
write!(f, "({})", self.actual)?;
if self.max != usize::MAX {
write!(f, " <= {}", self.max)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct StringSizeRule<const MIN: usize, const MAX: usize>;
impl<const MIN: usize, const MAX: usize> ValidateString for StringSizeRule<MIN, MAX> {
type Error = StringSizeOutOfRangeError;
fn validate_str(s: &str) -> Result<(), Self::Error> {
match s.len() {
n if n < MIN || n > MAX => Err(StringSizeOutOfRangeError {
min: MIN,
max: MAX,
actual: n,
}),
_ => Ok(()),
}
}
}
pub type StringExactSizeRule<const N: usize> = StringSizeRule<N, N>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct StringMinSizeRule<const MIN: usize>;
impl<const MIN: usize> ValidateString for StringMinSizeRule<MIN> {
type Error = StringSizeOutOfRangeError;
fn validate_str(s: &str) -> Result<(), Self::Error> {
match s.len() {
n if n < MIN => Err(StringSizeOutOfRangeError {
min: MIN,
max: usize::MAX,
actual: n,
}),
_ => Ok(()),
}
}
}
pub type StringNonEmptyRule = StringMinSizeRule<1>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct StringMaxSizeRule<const MAX: usize>;
impl<const MAX: usize> ValidateString for StringMaxSizeRule<MAX> {
type Error = StringSizeOutOfRangeError;
fn validate_str(s: &str) -> Result<(), Self::Error> {
match s.len() {
n if n > MAX => Err(StringSizeOutOfRangeError {
min: 0,
max: MAX,
actual: n,
}),
_ => Ok(()),
}
}
}
#[derive(Debug, Error)]
pub enum EitherError<L, R> {
#[error("left: {0}")]
Left(L),
#[error("right: {0}")]
Right(R),
}
impl<L, R> EitherError<L, R> {
pub fn is_left(&self) -> bool {
matches!(self, EitherError::Left(_))
}
pub fn is_right(&self) -> bool {
matches!(self, EitherError::Right(_))
}
pub fn discriminant(&self) -> core::mem::Discriminant<Self> {
core::mem::discriminant(self)
}
}
#[derive(Debug, Error)]
pub struct BothError<L, R> {
pub left: L,
pub right: R,
}
#[derive(Debug, Error)]
#[error("negation passed")]
pub struct NegationPassedError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(transparent)]
pub struct Conj<L: ValidateString, R: ValidateString> {
_l: PhantomData<L>,
_r: PhantomData<R>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(transparent)]
pub struct Disj<L: ValidateString, R: ValidateString> {
_l: PhantomData<L>,
_r: PhantomData<R>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(transparent)]
pub struct Neg<R: ValidateString> {
_r: PhantomData<R>,
}
impl<L: ValidateString, R: ValidateString> ValidateString for Conj<L, R> {
type Error = EitherError<L::Error, R::Error>;
fn validate_str(s: &str) -> Result<(), Self::Error> {
L::validate_str(s).map_err(EitherError::Left)?;
R::validate_str(s).map_err(EitherError::Right)?;
Ok(())
}
}
impl<L: ValidateString, R: ValidateString> ValidateString for Disj<L, R> {
type Error = BothError<L::Error, R::Error>;
fn validate_str(s: &str) -> Result<(), Self::Error> {
match L::validate_str(s) {
Ok(()) => Ok(()),
Err(el) => match R::validate_str(s) {
Ok(()) => Ok(()),
Err(er) => Err(BothError {
left: el,
right: er,
}),
},
}
}
}
impl<R: ValidateString> ValidateString for Neg<R> {
type Error = NegationPassedError;
fn validate_str(s: &str) -> Result<(), Self::Error> {
match R::validate_str(s) {
Ok(()) => Err(NegationPassedError),
Err(_) => Ok(()),
}
}
}
pub struct StringHadNonAsciiError;
pub struct StringAsciiRule;
fast_rule!(
StringAsciiRule,
err = StringHadNonAsciiError,
msg = "string had non-ASCII bytes",
|s: &str| s.as_bytes().iter().all(|&b| b.is_ascii())
);