use crate::{ValidationError, Validator};
pub trait HasLength {
fn length(&self) -> usize;
}
impl HasLength for str {
fn length(&self) -> usize {
self.chars().count()
}
}
impl<T> HasLength for [T] {
fn length(&self) -> usize {
self.len()
}
}
impl<U: HasLength + ?Sized> HasLength for &U {
fn length(&self) -> usize {
U::length(self)
}
}
#[cfg(feature = "alloc")]
impl HasLength for alloc::string::String {
fn length(&self) -> usize {
self.as_str().chars().count()
}
}
#[cfg(feature = "alloc")]
impl<T> HasLength for alloc::vec::Vec<T> {
fn length(&self) -> usize {
self.len()
}
}
pub struct NonEmpty;
impl<T: HasLength + ?Sized> Validator<T> for NonEmpty {
type Error = ValidationError;
fn validate(value: &T) -> Result<(), Self::Error> {
if value.length() > 0 {
Ok(())
} else {
Err(ValidationError::new("non_empty", "value must not be empty"))
}
}
}
pub struct MinLen<const MIN: usize>;
impl<const MIN: usize, T: HasLength + ?Sized> Validator<T> for MinLen<MIN> {
type Error = ValidationError;
fn validate(value: &T) -> Result<(), Self::Error> {
if value.length() >= MIN {
Ok(())
} else {
Err(ValidationError::new("min_len", "value is too short"))
}
}
}
pub struct MaxLen<const MAX: usize>;
impl<const MAX: usize, T: HasLength + ?Sized> Validator<T> for MaxLen<MAX> {
type Error = ValidationError;
fn validate(value: &T) -> Result<(), Self::Error> {
if value.length() <= MAX {
Ok(())
} else {
Err(ValidationError::new("max_len", "value is too long"))
}
}
}
pub struct LenRange<const MIN: usize, const MAX: usize>;
impl<const MIN: usize, const MAX: usize, T: HasLength + ?Sized> Validator<T>
for LenRange<MIN, MAX>
{
type Error = ValidationError;
fn validate(value: &T) -> Result<(), Self::Error> {
let len = value.length();
if len >= MIN && len <= MAX {
Ok(())
} else {
Err(ValidationError::new(
"len_range",
"value length is out of range",
))
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
#[test]
fn has_length_counts_chars_and_elements() {
assert_eq!("héllo".length(), 5);
assert_eq!([1, 2, 3][..].length(), 3);
let slice: &[u8] = b"abc";
assert_eq!(slice.length(), 3);
}
#[test]
fn non_empty_rules() {
assert!(NonEmpty::validate("x").is_ok());
assert_eq!(NonEmpty::validate("").unwrap_err().code(), "non_empty");
}
#[test]
fn min_max_len_boundaries() {
assert!(MinLen::<2>::validate("ab").is_ok());
assert!(MinLen::<2>::validate("a").is_err());
assert!(MaxLen::<2>::validate("ab").is_ok());
assert!(MaxLen::<2>::validate("abc").is_err());
}
#[test]
fn len_range_inclusive_bounds() {
assert!(LenRange::<2, 4>::validate("ab").is_ok());
assert!(LenRange::<2, 4>::validate("abcd").is_ok());
assert!(LenRange::<2, 4>::validate("a").is_err());
assert!(LenRange::<2, 4>::validate("abcde").is_err());
}
}