Skip to main content

tanzim_validate/
either.rs

1use crate::error::{Error, ErrorKind};
2use crate::{Meta, Validator};
3use tanzim_value::Value;
4
5/// (`either` feature) Accepts the value if **either** of two validators accepts it.
6///
7/// The first validator is tried first; if it succeeds (possibly coercing the value), its
8/// result is kept. Otherwise the second validator is tried against the *original* value, so
9/// a partial coercion from the first attempt is never observed. If both fail, the two
10/// errors are combined into a single [`ErrorKind::Either`] that reports what each expected.
11pub struct Either {
12    meta: Meta,
13    first: Box<dyn Validator>,
14    second: Box<dyn Validator>,
15}
16
17impl Either {
18    /// Attach human-facing metadata (name, description, examples, default, output conversion).
19    pub fn with_meta(mut self, meta: Meta) -> Self {
20        self.meta = meta;
21        self
22    }
23
24    pub fn new(
25        first: impl Into<Box<dyn Validator>>,
26        second: impl Into<Box<dyn Validator>>,
27    ) -> Self {
28        Self {
29            meta: Meta::default(),
30            first: first.into(),
31            second: second.into(),
32        }
33    }
34}
35
36crate::impl_meta_methods!(Either);
37
38impl Validator for Either {
39    fn meta(&self) -> &Meta {
40        &self.meta
41    }
42
43    fn meta_mut(&mut self) -> &mut Meta {
44        &mut self.meta
45    }
46
47    fn check(&self, value: &mut Value) -> Result<(), Error> {
48        // Validate on a copy so a failing branch never leaves a half-coerced value behind;
49        // only the branch that succeeds is committed back.
50        let mut candidate = value.clone();
51        let first_error = match self.first.validate(&mut candidate) {
52            Ok(()) => {
53                *value = candidate;
54                return Ok(());
55            }
56            Err(error) => error,
57        };
58
59        let mut candidate = value.clone();
60        let second_error = match self.second.validate(&mut candidate) {
61            Ok(()) => {
62                *value = candidate;
63                return Ok(());
64            }
65            Err(error) => error,
66        };
67
68        Err(Error::new(ErrorKind::Either {
69            first: Box::new(first_error),
70            second: Box::new(second_error),
71        }))
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use crate::{Bool, Integer};
79
80    #[test]
81    fn accepts_when_either_matches() {
82        let validator = Either::new(Integer::new(), Bool::new());
83        assert!(validator.validate(&mut Value::Int(3)).is_ok());
84        assert!(validator.validate(&mut Value::Bool(true)).is_ok());
85    }
86
87    #[test]
88    fn commits_coercion_of_winning_branch() {
89        let validator = Either::new(Integer::new(), Bool::new());
90        let mut value = Value::String("5".into());
91        validator.validate(&mut value).unwrap();
92        assert_eq!(value, Value::Int(5));
93    }
94
95    #[test]
96    fn original_value_preserved_for_second_attempt() {
97        // Float would reject a bool and could not coerce it; the second branch must still
98        // see the original Bool, not whatever the first branch left behind.
99        let validator = Either::new(Integer::new(), Bool::new());
100        let mut value = Value::Bool(true);
101        validator.validate(&mut value).unwrap();
102        assert_eq!(value, Value::Bool(true));
103    }
104
105    #[test]
106    fn combines_errors_when_both_fail() {
107        let validator = Either::new(Bool::new(), Integer::new());
108        let mut value = Value::String("nope".into());
109        let error = validator.validate(&mut value).unwrap_err();
110        assert!(matches!(error.kind, ErrorKind::Either { .. }));
111        // value untouched on total failure
112        assert_eq!(value, Value::String("nope".into()));
113    }
114}