Skip to main content

reliakit_validate/
valid.rs

1use crate::Validate;
2use core::ops::Deref;
3
4/// A value that has been successfully validated.
5///
6/// `Valid<T>` is a zero-cost wrapper that carries proof of validation in the
7/// type system. Construction requires the value to pass [`Validate::validate`].
8/// Once inside `Valid<T>`, the value is considered correct by the validation
9/// rules of `T`.
10///
11/// # Examples
12///
13/// ```
14/// use reliakit_validate::{Validate, Valid, ValidationError};
15///
16/// struct Age(u8);
17///
18/// impl Validate for Age {
19///     type Error = ValidationError;
20///     fn validate(&self) -> Result<(), Self::Error> {
21///         if self.0 > 120 {
22///             return Err(ValidationError::new("age must not exceed 120"));
23///         }
24///         Ok(())
25///     }
26/// }
27///
28/// let age = Valid::new(Age(25)).unwrap();
29/// assert_eq!(age.0, 25);
30/// ```
31#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub struct Valid<T>(T);
33
34impl<T: Validate> Valid<T> {
35    /// Validates and wraps the value. Returns an error if validation fails.
36    pub fn new(value: T) -> Result<Self, T::Error> {
37        value.validate()?;
38        Ok(Self(value))
39    }
40
41    /// Returns a reference to the inner value.
42    pub fn get(&self) -> &T {
43        &self.0
44    }
45
46    /// Consumes the wrapper and returns the inner value.
47    pub fn into_inner(self) -> T {
48        self.0
49    }
50}
51
52impl<T: Validate> Deref for Valid<T> {
53    type Target = T;
54
55    fn deref(&self) -> &Self::Target {
56        &self.0
57    }
58}
59
60impl<T: Validate + core::fmt::Display> core::fmt::Display for Valid<T> {
61    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
62        core::fmt::Display::fmt(&self.0, f)
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::Valid;
69    use crate::{Validate, ValidationError};
70
71    struct MinAge(u8);
72
73    impl Validate for MinAge {
74        type Error = ValidationError;
75        fn validate(&self) -> Result<(), Self::Error> {
76            if self.0 < 18 {
77                return Err(ValidationError::new("must be at least 18"));
78            }
79            Ok(())
80        }
81    }
82
83    #[test]
84    fn valid_new_accepts_valid_value() {
85        let v = Valid::new(MinAge(18)).unwrap();
86        assert_eq!(v.get().0, 18);
87    }
88
89    #[test]
90    fn valid_new_rejects_invalid_value() {
91        assert!(Valid::new(MinAge(17)).is_err());
92    }
93
94    #[test]
95    fn valid_into_inner() {
96        let v = Valid::new(MinAge(25)).unwrap();
97        assert_eq!(v.into_inner().0, 25);
98    }
99
100    #[test]
101    fn valid_deref() {
102        let v = Valid::new(MinAge(30)).unwrap();
103        assert_eq!((*v).0, 30);
104    }
105
106    #[test]
107    fn valid_get() {
108        let v = Valid::new(MinAge(21)).unwrap();
109        assert_eq!(v.get().0, 21);
110    }
111
112    struct NamedAge(u8);
113
114    impl Validate for NamedAge {
115        type Error = ValidationError;
116        fn validate(&self) -> Result<(), Self::Error> {
117            if self.0 < 18 {
118                return Err(ValidationError::new("must be at least 18"));
119            }
120            Ok(())
121        }
122    }
123
124    impl core::fmt::Display for NamedAge {
125        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
126            write!(f, "age:{}", self.0)
127        }
128    }
129
130    #[test]
131    fn valid_display_delegates_to_inner() {
132        use alloc::string::ToString;
133        let v = Valid::new(NamedAge(25)).unwrap();
134        assert_eq!(v.to_string(), "age:25");
135    }
136}