Skip to main content

terminals_core/primitives/
validation.rs

1/// Validated<T> — newtype guaranteeing data has passed validation.
2/// Equivalent to TS branded type `T & { _validated: true }`.
3/// Use `.unbrand()` to consume. No `.value` accessor.
4pub struct Validated<T>(T);
5
6impl<T> Validated<T> {
7    pub(crate) fn new(value: T) -> Self {
8        Self(value)
9    }
10    pub fn unbrand(self) -> T {
11        self.0
12    }
13    pub fn inner(&self) -> &T {
14        &self.0
15    }
16}
17
18/// Untrusted<T> — newtype marking data as unvalidated.
19/// Must pass through a validation predicate to become Validated<T>.
20pub struct Untrusted<T>(T);
21
22impl<T> Untrusted<T> {
23    pub fn new(value: T) -> Self {
24        Self(value)
25    }
26
27    /// Validate with a predicate. Returns Some(Validated<T>) if valid, None otherwise.
28    pub fn validate<F: FnOnce(&T) -> bool>(self, predicate: F) -> Option<Validated<T>> {
29        if predicate(&self.0) {
30            Some(Validated::new(self.0))
31        } else {
32            None
33        }
34    }
35
36    /// Validate with a fallible function. Returns Result<Validated<T>, E>.
37    pub fn try_validate<E, F: FnOnce(&T) -> Result<(), E>>(
38        self,
39        validator: F,
40    ) -> Result<Validated<T>, E> {
41        validator(&self.0)?;
42        Ok(Validated::new(self.0))
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn test_validated_unbrand() {
52        let validated = Validated::new(42);
53        assert_eq!(validated.unbrand(), 42);
54    }
55
56    #[test]
57    fn test_untrusted_validate() {
58        let untrusted = Untrusted::new("hello");
59        let validated = untrusted.validate(|s| !s.is_empty());
60        assert!(validated.is_some());
61    }
62
63    #[test]
64    fn test_untrusted_validate_fails() {
65        let untrusted = Untrusted::new("");
66        let validated = untrusted.validate(|s| !s.is_empty());
67        assert!(validated.is_none());
68    }
69}