Skip to main content

type_lib/
validator.rs

1//! The [`Validator`] trait: reusable, type-level validation rules.
2
3/// A reusable validation rule applied to values of type `T`.
4///
5/// A `Validator` is a *type-level* predicate. It carries no state and is never
6/// instantiated — you implement it on a zero-sized marker type and the rule is
7/// selected purely through the type system. Pairing a value with the validator
8/// that vouched for it is exactly what [`Refined`](crate::Refined) does, at no
9/// runtime cost.
10///
11/// # Choosing the value type
12///
13/// `T` is `?Sized`, so a rule can target an unsized type such as `str` or `[u8]`
14/// and be reused for the owned forms by being generic over a borrow. The
15/// `NonEmpty` rule below works for `str`, `String`, and `&str` alike because it
16/// is written over `S: AsRef<str>`:
17///
18/// ```rust
19/// use type_lib::{ValidationError, Validator};
20///
21/// struct NonEmpty;
22///
23/// impl<S: AsRef<str> + ?Sized> Validator<S> for NonEmpty {
24///     type Error = ValidationError;
25///
26///     fn validate(value: &S) -> Result<(), Self::Error> {
27///         if value.as_ref().is_empty() {
28///             Err(ValidationError::new("non_empty", "value must not be empty"))
29///         } else {
30///             Ok(())
31///         }
32///     }
33/// }
34///
35/// assert!(NonEmpty::validate("hello").is_ok());
36/// assert!(NonEmpty::validate("").is_err());
37/// ```
38///
39/// # Custom error types
40///
41/// [`Error`](Validator::Error) is an associated type, so a rule can report a
42/// rich, structured failure instead of the bundled [`ValidationError`]:
43///
44/// ```rust
45/// use type_lib::Validator;
46///
47/// #[derive(Debug, PartialEq)]
48/// struct TooLong {
49///     limit: usize,
50///     actual: usize,
51/// }
52///
53/// struct MaxLen8;
54///
55/// impl Validator<str> for MaxLen8 {
56///     type Error = TooLong;
57///
58///     fn validate(value: &str) -> Result<(), Self::Error> {
59///         let actual = value.chars().count();
60///         if actual > 8 {
61///             Err(TooLong { limit: 8, actual })
62///         } else {
63///             Ok(())
64///         }
65///     }
66/// }
67///
68/// assert_eq!(
69///     MaxLen8::validate("far-too-long"),
70///     Err(TooLong { limit: 8, actual: 12 }),
71/// );
72/// ```
73///
74/// [`ValidationError`]: crate::ValidationError
75pub trait Validator<T: ?Sized> {
76    /// The failure produced when a value violates the rule.
77    ///
78    /// Use [`ValidationError`](crate::ValidationError) for simple cases, or a
79    /// bespoke type when callers need to inspect structured details of the
80    /// failure.
81    type Error;
82
83    /// Checks `value` against the rule.
84    ///
85    /// Returns `Ok(())` when the value satisfies the invariant, or
86    /// `Err(Self::Error)` describing why it does not. This is a pure predicate:
87    /// it never mutates or transforms the value.
88    ///
89    /// # Errors
90    ///
91    /// Returns [`Self::Error`](Validator::Error) when `value` fails the rule.
92    fn validate(value: &T) -> Result<(), Self::Error>;
93}
94
95#[cfg(test)]
96mod tests {
97    #![allow(clippy::unwrap_used, clippy::expect_used)]
98
99    use super::*;
100    use crate::ValidationError;
101
102    struct NonEmpty;
103
104    impl<S: AsRef<str> + ?Sized> Validator<S> for NonEmpty {
105        type Error = ValidationError;
106
107        fn validate(value: &S) -> Result<(), Self::Error> {
108            if value.as_ref().is_empty() {
109                Err(ValidationError::new("non_empty", "value must not be empty"))
110            } else {
111                Ok(())
112            }
113        }
114    }
115
116    #[test]
117    fn accepts_valid_value() {
118        assert!(NonEmpty::validate("hello").is_ok());
119    }
120
121    #[test]
122    fn rejects_invalid_value() {
123        let err = NonEmpty::validate("").unwrap_err();
124        assert_eq!(err.code(), "non_empty");
125    }
126
127    #[test]
128    fn rule_is_reusable_across_borrow_forms() {
129        // Same marker type, different value representations.
130        assert!(<NonEmpty as Validator<str>>::validate("ok").is_ok());
131        let owned = "ok";
132        assert!(<NonEmpty as Validator<&str>>::validate(&owned).is_ok());
133    }
134}