avocado_schema/core/constraint/string/
pattern.rs

1use crate::core::constraint::Constraint;
2use crate::core::value::FieldValue;
3use anyhow::{anyhow, Result};
4use regex::Regex;
5use serde::de::{Error, Visitor};
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use std::fmt::Formatter;
8
9#[derive(Clone, Debug)]
10pub struct Pattern {
11    pub pattern: Regex,
12}
13
14impl Serialize for Pattern {
15    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
16    where
17        S: Serializer,
18    {
19        serializer.serialize_str(self.pattern.as_str())
20    }
21}
22
23impl<'de> Deserialize<'de> for Pattern {
24    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
25    where
26        D: Deserializer<'de>,
27    {
28        deserializer.deserialize_string(PatternVisitor)
29    }
30}
31
32struct PatternVisitor;
33
34impl<'de> Visitor<'de> for PatternVisitor {
35    type Value = Pattern;
36
37    fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
38        write!(
39            formatter,
40            "string field [pattern] needs to be a valid regular expression"
41        )
42    }
43
44    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
45    where
46        E: Error,
47    {
48        let pattern = Regex::new(v).map_err(|e| Error::custom(e.to_string()))?;
49        Ok(Pattern { pattern })
50    }
51}
52
53impl Constraint for Pattern {
54    fn validate(&self, val: &FieldValue) -> Result<()> {
55        match val {
56            FieldValue::String(v) if !self.pattern.is_match(v.as_str()) => Err(anyhow!(format!(
57                "{} does not match pattern {} ({})",
58                self.pattern, v, "Pattern"
59            ))),
60            _ => Ok(()),
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use crate::core::constraint::string::pattern::Pattern;
68    use crate::core::constraint::Constraint;
69    use crate::core::value::FieldValue;
70
71    #[test]
72    fn test_pattern() {
73        let constraint = Pattern {
74            pattern: r"^\d{4}-\d{2}-\d{2}$".parse().unwrap(),
75        };
76
77        let value = FieldValue::String("2010-03-14".to_string());
78        assert!(constraint.validate(&value).is_ok());
79
80        let value = FieldValue::String("Not Match".to_string());
81        assert!(constraint.validate(&value).is_err());
82    }
83}