Skip to main content

wasm_dbms_api/dbms/validate/
chain.rs

1use std::fmt;
2
3use crate::prelude::{DbmsResult, Validate, Value};
4
5/// Validator that runs several validators in order.
6///
7/// The value is valid when every validator accepts it; the error of the first failing
8/// validator is returned. `#[derive(Table)]` uses it when a column declares more than one
9/// `#[validate(...)]` attribute, keeping the declaration order (top to bottom).
10///
11/// # Example
12///
13/// ```rust
14/// use wasm_dbms_api::prelude::{
15///     MaxStrlenValidator, MinStrlenValidator, Validate as _, ValidatorChain, Value,
16/// };
17///
18/// let validator = ValidatorChain::new(vec![
19///     Box::new(MinStrlenValidator(2)),
20///     Box::new(MaxStrlenValidator(5)),
21/// ]);
22/// assert!(validator.validate(&Value::Text("abc".into())).is_ok());
23/// assert!(validator.validate(&Value::Text("a".into())).is_err());
24/// assert!(validator.validate(&Value::Text("abcdef".into())).is_err());
25/// ```
26pub struct ValidatorChain(Vec<Box<dyn Validate>>);
27
28impl ValidatorChain {
29    /// Creates a chain running `validators` in the given order.
30    pub fn new(validators: Vec<Box<dyn Validate>>) -> Self {
31        Self(validators)
32    }
33}
34
35impl fmt::Debug for ValidatorChain {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        f.debug_struct("ValidatorChain")
38            .field("len", &self.0.len())
39            .finish()
40    }
41}
42
43impl Validate for ValidatorChain {
44    fn validate(&self, value: &Value) -> DbmsResult<()> {
45        self.0
46            .iter()
47            .try_for_each(|validator| validator.validate(value))
48    }
49}
50
51#[cfg(test)]
52mod tests {
53
54    use super::*;
55    use crate::prelude::{DbmsError, MaxStrlenValidator, MinStrlenValidator};
56
57    struct FailingValidator(&'static str);
58
59    impl Validate for FailingValidator {
60        fn validate(&self, _value: &Value) -> DbmsResult<()> {
61            Err(DbmsError::Validation(self.0.to_string()))
62        }
63    }
64
65    #[test]
66    fn test_should_accept_value_valid_for_every_validator() {
67        let validator = ValidatorChain::new(vec![
68            Box::new(MinStrlenValidator(2)),
69            Box::new(MaxStrlenValidator(5)),
70        ]);
71        assert!(validator.validate(&Value::Text("abc".into())).is_ok());
72        assert!(validator.validate(&Value::Text("a".into())).is_err());
73        assert!(validator.validate(&Value::Text("abcdef".into())).is_err());
74    }
75
76    #[test]
77    fn test_should_accept_any_value_when_empty() {
78        let validator = ValidatorChain::new(vec![]);
79        assert!(validator.validate(&Value::Text("anything".into())).is_ok());
80    }
81
82    #[test]
83    fn test_should_return_first_error() {
84        let validator = ValidatorChain::new(vec![
85            Box::new(FailingValidator("first")),
86            Box::new(FailingValidator("second")),
87        ]);
88        let err = validator.validate(&Value::Text("x".into())).unwrap_err();
89        assert!(matches!(err, DbmsError::Validation(message) if message == "first"));
90    }
91}