Skip to main content

type_lib/rules/
length.rs

1//! Length-based rules for strings, slices, and other measurable values.
2//!
3//! Each rule here works on any type that implements [`HasLength`]. The crate
4//! ships impls for `str` and `[T]` (always), and for `String` and `Vec<T>` when
5//! the `alloc` feature is enabled, plus a blanket impl for shared references so a
6//! rule applies equally to `str` and `&str`.
7//!
8//! For strings, "length" is the number of [`char`]s (Unicode scalar values), not
9//! the number of bytes — the count a human means by "at most 20 characters". For
10//! slices and vectors it is the number of elements.
11
12use crate::{ValidationError, Validator};
13
14/// A value with a measurable length, used by the length rules.
15///
16/// Implement this for your own container types to make them eligible for
17/// [`NonEmpty`], [`MinLen`], [`MaxLen`], and [`LenRange`].
18///
19/// # Examples
20///
21/// ```rust
22/// use type_lib::rules::HasLength;
23///
24/// assert_eq!("héllo".length(), 5); // chars, not bytes
25/// assert_eq!([1, 2, 3][..].length(), 3);
26/// ```
27pub trait HasLength {
28    /// Returns the length of the value: chars for strings, elements otherwise.
29    fn length(&self) -> usize;
30}
31
32impl HasLength for str {
33    fn length(&self) -> usize {
34        self.chars().count()
35    }
36}
37
38impl<T> HasLength for [T] {
39    fn length(&self) -> usize {
40        self.len()
41    }
42}
43
44impl<U: HasLength + ?Sized> HasLength for &U {
45    fn length(&self) -> usize {
46        U::length(self)
47    }
48}
49
50#[cfg(feature = "alloc")]
51impl HasLength for alloc::string::String {
52    fn length(&self) -> usize {
53        self.as_str().chars().count()
54    }
55}
56
57#[cfg(feature = "alloc")]
58impl<T> HasLength for alloc::vec::Vec<T> {
59    fn length(&self) -> usize {
60        self.len()
61    }
62}
63
64/// Accepts any value whose [`length`](HasLength::length) is greater than zero.
65///
66/// # Examples
67///
68/// ```rust
69/// use type_lib::rules::NonEmpty;
70/// use type_lib::Validator;
71///
72/// assert!(NonEmpty::validate("hello").is_ok());
73/// assert!(NonEmpty::validate("").is_err());
74/// assert!(NonEmpty::validate(&[1, 2, 3][..]).is_ok());
75/// ```
76pub struct NonEmpty;
77
78impl<T: HasLength + ?Sized> Validator<T> for NonEmpty {
79    type Error = ValidationError;
80
81    fn validate(value: &T) -> Result<(), Self::Error> {
82        if value.length() > 0 {
83            Ok(())
84        } else {
85            Err(ValidationError::new("non_empty", "value must not be empty"))
86        }
87    }
88}
89
90/// Accepts values with at least `MIN` elements (inclusive).
91///
92/// # Examples
93///
94/// ```rust
95/// use type_lib::rules::MinLen;
96/// use type_lib::Validator;
97///
98/// assert!(MinLen::<3>::validate("abc").is_ok());
99/// assert!(MinLen::<3>::validate("ab").is_err());
100/// ```
101pub struct MinLen<const MIN: usize>;
102
103impl<const MIN: usize, T: HasLength + ?Sized> Validator<T> for MinLen<MIN> {
104    type Error = ValidationError;
105
106    fn validate(value: &T) -> Result<(), Self::Error> {
107        if value.length() >= MIN {
108            Ok(())
109        } else {
110            Err(ValidationError::new("min_len", "value is too short"))
111        }
112    }
113}
114
115/// Accepts values with at most `MAX` elements (inclusive).
116///
117/// # Examples
118///
119/// ```rust
120/// use type_lib::rules::MaxLen;
121/// use type_lib::Validator;
122///
123/// assert!(MaxLen::<5>::validate("hello").is_ok());
124/// assert!(MaxLen::<5>::validate("too long").is_err());
125/// ```
126pub struct MaxLen<const MAX: usize>;
127
128impl<const MAX: usize, T: HasLength + ?Sized> Validator<T> for MaxLen<MAX> {
129    type Error = ValidationError;
130
131    fn validate(value: &T) -> Result<(), Self::Error> {
132        if value.length() <= MAX {
133            Ok(())
134        } else {
135            Err(ValidationError::new("max_len", "value is too long"))
136        }
137    }
138}
139
140/// Accepts values whose length is in the inclusive range `MIN..=MAX`.
141///
142/// # Examples
143///
144/// ```rust
145/// use type_lib::rules::LenRange;
146/// use type_lib::Validator;
147///
148/// // A typical username constraint: 3 to 16 characters.
149/// assert!(LenRange::<3, 16>::validate("alice").is_ok());
150/// assert!(LenRange::<3, 16>::validate("ab").is_err());
151/// assert!(LenRange::<3, 16>::validate("this-name-is-far-too-long").is_err());
152/// ```
153pub struct LenRange<const MIN: usize, const MAX: usize>;
154
155impl<const MIN: usize, const MAX: usize, T: HasLength + ?Sized> Validator<T>
156    for LenRange<MIN, MAX>
157{
158    type Error = ValidationError;
159
160    fn validate(value: &T) -> Result<(), Self::Error> {
161        let len = value.length();
162        if len >= MIN && len <= MAX {
163            Ok(())
164        } else {
165            Err(ValidationError::new(
166                "len_range",
167                "value length is out of range",
168            ))
169        }
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    #![allow(clippy::unwrap_used, clippy::expect_used)]
176
177    use super::*;
178
179    #[test]
180    fn has_length_counts_chars_and_elements() {
181        assert_eq!("héllo".length(), 5);
182        assert_eq!([1, 2, 3][..].length(), 3);
183
184        // The blanket impl for references lets a rule see through one `&`.
185        let slice: &[u8] = b"abc";
186        assert_eq!(slice.length(), 3);
187    }
188
189    #[test]
190    fn non_empty_rules() {
191        assert!(NonEmpty::validate("x").is_ok());
192        assert_eq!(NonEmpty::validate("").unwrap_err().code(), "non_empty");
193    }
194
195    #[test]
196    fn min_max_len_boundaries() {
197        assert!(MinLen::<2>::validate("ab").is_ok());
198        assert!(MinLen::<2>::validate("a").is_err());
199        assert!(MaxLen::<2>::validate("ab").is_ok());
200        assert!(MaxLen::<2>::validate("abc").is_err());
201    }
202
203    #[test]
204    fn len_range_inclusive_bounds() {
205        assert!(LenRange::<2, 4>::validate("ab").is_ok());
206        assert!(LenRange::<2, 4>::validate("abcd").is_ok());
207        assert!(LenRange::<2, 4>::validate("a").is_err());
208        assert!(LenRange::<2, 4>::validate("abcde").is_err());
209    }
210}