1use std::borrow::Cow;
3use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
4use std::marker::PhantomData;
5
6use crate::{Validator, Violation};
7
8pub trait Length {
13 fn length(&self) -> usize;
15}
16
17impl Length for str {
18 fn length(&self) -> usize {
19 self.chars().count()
20 }
21}
22
23impl Length for String {
24 fn length(&self) -> usize {
25 self.as_str().length()
26 }
27}
28
29impl<T> Length for [T] {
30 fn length(&self) -> usize {
31 self.len()
32 }
33}
34
35impl<T, const N: usize> Length for [T; N] {
36 fn length(&self) -> usize {
37 N
38 }
39}
40
41macro_rules! impl_length {
42 ($($ty:ty => [$($param:tt)*];)*) => {
43 $(
44 impl<$($param)*> Length for $ty {
45 fn length(&self) -> usize {
46 self.len()
47 }
48 }
49 )*
50 };
51}
52
53impl_length! {
54 Vec<T> => [T];
55 VecDeque<T> => [T];
56 BTreeSet<T> => [T];
57 HashSet<T, S> => [T, S];
58 BTreeMap<K, V> => [K, V];
59 HashMap<K, V, S> => [K, V, S];
60}
61
62impl<T: Length + ?Sized> Length for &T {
63 fn length(&self) -> usize {
64 (**self).length()
65 }
66}
67
68impl<T: Length + ?Sized> Length for Box<T> {
69 fn length(&self) -> usize {
70 (**self).length()
71 }
72}
73
74impl<T: Length + ToOwned + ?Sized> Length for Cow<'_, T> {
75 fn length(&self) -> usize {
76 (**self).length()
77 }
78}
79
80pub struct NonEmpty;
84
85impl<T: Length + ?Sized> Validator<T> for NonEmpty {
86 fn validate(value: &T) -> Result<(), Violation> {
87 if value.length() == 0 {
88 Err(Violation::new("non_empty", "must not be empty"))
89 } else {
90 Ok(())
91 }
92 }
93}
94
95pub struct Len<const MIN: usize, const MAX: usize = { usize::MAX }>;
113
114pub type MinLen<const N: usize> = Len<N>;
116
117pub type MaxLen<const N: usize> = Len<0, N>;
119
120impl<T: Length + ?Sized, const MIN: usize, const MAX: usize> Validator<T> for Len<MIN, MAX> {
121 fn validate(value: &T) -> Result<(), Violation> {
122 let len = value.length();
123 if (MIN..=MAX).contains(&len) {
124 return Ok(());
125 }
126 let violation = match (MIN, MAX) {
127 (min, usize::MAX) => {
128 Violation::new("min_len", format!("length must be at least {}", min))
129 .with_param("min", min)
130 }
131 (0, max) => Violation::new("max_len", format!("length must be at most {}", max))
132 .with_param("max", max),
133 (min, max) => {
134 Violation::new("len", format!("length must be between {} and {}", min, max))
135 .with_param("min", min)
136 .with_param("max", max)
137 }
138 };
139 Err(violation)
140 }
141}
142
143pub trait Integer {
145 fn to_i128(&self) -> Option<i128>;
147}
148
149macro_rules! impl_integer {
150 ($($ty:ty),*) => {
151 $(
152 impl Integer for $ty {
153 fn to_i128(&self) -> Option<i128> {
154 i128::try_from(*self).ok()
155 }
156 }
157 )*
158 };
159}
160
161impl_integer!(
162 i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
163);
164
165pub struct Range<const MIN: i128, const MAX: i128>;
182
183pub type Min<const N: i128> = Range<N, { i128::MAX }>;
185
186pub type Max<const N: i128> = Range<{ i128::MIN }, N>;
188
189impl<T: Integer + ?Sized, const MIN: i128, const MAX: i128> Validator<T> for Range<MIN, MAX> {
190 fn validate(value: &T) -> Result<(), Violation> {
191 let valid = match value.to_i128() {
193 Some(value) => (MIN..=MAX).contains(&value),
194 None => MAX == i128::MAX,
195 };
196 if valid {
197 return Ok(());
198 }
199 let violation = match (MIN, MAX) {
200 (min, i128::MAX) => {
201 Violation::new("min", format!("must be at least {}", min)).with_param("min", min)
202 }
203 (i128::MIN, max) => {
204 Violation::new("max", format!("must be at most {}", max)).with_param("max", max)
205 }
206 (min, max) => Violation::new("range", format!("must be between {} and {}", min, max))
207 .with_param("min", min)
208 .with_param("max", max),
209 };
210 Err(violation)
211 }
212}
213
214pub struct Email;
229
230impl<T: AsRef<str> + ?Sized> Validator<T> for Email {
231 fn validate(value: &T) -> Result<(), Violation> {
232 let value = value.as_ref();
233 let valid = match value.split_once('@') {
234 Some((local, domain)) => {
235 !local.is_empty()
236 && !domain.contains('@')
237 && !value.chars().any(char::is_whitespace)
238 && domain.contains('.')
239 && domain.split('.').all(|label| !label.is_empty())
240 }
241 None => false,
242 };
243 if valid {
244 Ok(())
245 } else {
246 Err(Violation::new("email", "must be an email address"))
247 }
248 }
249}
250
251pub struct Each<V>(PhantomData<fn() -> V>);
266
267impl<V> Each<V> {
268 fn validate_items<'a, T: 'a, I>(items: I) -> Result<(), Violation>
269 where
270 V: Validator<T>,
271 I: IntoIterator<Item = &'a T>,
272 {
273 for (index, item) in items.into_iter().enumerate() {
274 if let Err(violation) = V::validate(item) {
275 let message = format!("item {}: {}", index, violation.message());
276 let mut rv = Violation::new(violation.code().to_string(), message);
277 for (name, value) in violation.params() {
278 rv = rv.with_param(name, value.clone());
279 }
280 return Err(rv.with_param("index", index));
281 }
282 }
283 Ok(())
284 }
285}
286
287macro_rules! impl_each {
288 ($($ty:ty => [$($param:tt)*];)*) => {
289 $(
290 impl<V: Validator<T>, $($param)*> Validator<$ty> for Each<V> {
291 fn validate(value: &$ty) -> Result<(), Violation> {
292 Each::<V>::validate_items(value.iter())
293 }
294 }
295 )*
296 };
297}
298
299impl_each! {
300 [T] => [T];
301 Vec<T> => [T];
302 VecDeque<T> => [T];
303 BTreeSet<T> => [T];
304 HashSet<T, S> => [T, S];
305 Option<T> => [T];
306}
307
308impl<V: Validator<T>, T, const N: usize> Validator<[T; N]> for Each<V> {
309 fn validate(value: &[T; N]) -> Result<(), Violation> {
310 Each::<V>::validate_items(value.iter())
311 }
312}