Skip to main content

yaml_schema/validation/
strings.rs

1use log::debug;
2use regex::Regex;
3
4use crate::Context;
5use crate::Result;
6use crate::Validator;
7use crate::schemas::StringSchema;
8
9impl Validator for StringSchema {
10    fn validate(&self, context: &Context, value: &saphyr::MarkedYaml) -> Result<()> {
11        let errors = self.do_validate(value);
12        if !errors.is_empty() {
13            for error in errors {
14                context.add_error(value, error);
15            }
16        }
17        Ok(())
18    }
19}
20
21impl StringSchema {
22    fn do_validate(&self, value: &saphyr::MarkedYaml) -> Vec<String> {
23        debug!("do_validate: {:?}", value.data);
24        let mut errors = Vec::new();
25
26        if let saphyr::YamlData::Value(scalar) = &value.data
27            && let saphyr::Scalar::String(s) = scalar
28        {
29            // TODO: add enum validation
30            let enum_strings = None;
31            debug!("enum_strings: {enum_strings:?}");
32            validate_string(
33                &mut errors,
34                self.min_length,
35                self.max_length,
36                self.pattern.as_ref(),
37                enum_strings.as_ref(),
38                s,
39            );
40        } else {
41            errors.push(format!("Expected a string, but got: {:?}", value.data));
42        }
43        errors
44    }
45}
46
47/// Just trying to isolate the actual validation into a function that doesn't take a context
48pub fn validate_string(
49    errors: &mut Vec<String>,
50    min_length: Option<usize>,
51    max_length: Option<usize>,
52    pattern: Option<&Regex>,
53    r#enum: Option<&Vec<String>>,
54    str_value: &str,
55) {
56    if let Some(min_length) = min_length
57        && str_value.len() < min_length
58    {
59        errors.push(format!("String is too short! (min length: {min_length})"));
60    }
61    if let Some(max_length) = max_length
62        && str_value.len() > max_length
63    {
64        errors.push(format!("String is too long! (max length: {max_length})"));
65    }
66    if let Some(regex) = pattern
67        && !regex.is_match(str_value)
68    {
69        errors.push(format!(
70            "String does not match regular expression {}!",
71            regex.as_str()
72        ));
73    }
74    if let Some(enum_values) = r#enum
75        && !enum_values.contains(&str_value.to_string())
76    {
77        errors.push(format!("String is not in enum: {enum_values:?}"));
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use crate::Engine;
84    use crate::RootSchema;
85    use crate::YamlSchema;
86    use saphyr::LoadableYamlNode;
87
88    use super::*;
89
90    #[test]
91    fn test_engine_validate_string() {
92        let schema = StringSchema::default();
93        let root_schema = RootSchema::new(YamlSchema::typed_string(schema));
94        let context = Engine::evaluate(&root_schema, "some string", false).unwrap();
95        assert!(!context.has_errors());
96    }
97
98    #[test]
99    fn test_engine_validate_string_with_min_length() {
100        let schema = StringSchema {
101            min_length: Some(5),
102            ..Default::default()
103        };
104        let root_schema = RootSchema::new(YamlSchema::typed_string(schema));
105        let context = Engine::evaluate(&root_schema, "hello", false).unwrap();
106        assert!(!context.has_errors());
107        let context = Engine::evaluate(&root_schema, "hell", false).unwrap();
108        assert!(context.has_errors());
109    }
110
111    #[test]
112    fn test_validate_string() {
113        let mut errors = Vec::new();
114        validate_string(&mut errors, None, None, None, None, "hello");
115        assert!(errors.is_empty());
116    }
117
118    #[test]
119    fn test_validate_string_with_min_length() {
120        let mut errors = Vec::new();
121        validate_string(&mut errors, Some(5), None, None, None, "hello");
122        assert!(errors.is_empty());
123        validate_string(&mut errors, Some(5), None, None, None, "hell");
124        assert!(!errors.is_empty());
125        assert_eq!(
126            errors.first().unwrap(),
127            "String is too short! (min length: 5)"
128        );
129    }
130
131    #[test]
132    fn test_string_schema_validation() {
133        let schema = StringSchema::default();
134        let docs = saphyr::MarkedYaml::load_from_str("Washington").unwrap();
135        let value = docs.first().unwrap();
136        let context = Context::default();
137        let result = schema.validate(&context, value);
138        assert!(result.is_ok());
139    }
140
141    #[test]
142    fn test_string_schema_doesnt_validate_object() {
143        let yaml = "an: [arbitrarily, nested, data, structure]";
144        let doc = saphyr::MarkedYaml::load_from_str(yaml).unwrap();
145        let marked_yaml = doc.first().unwrap();
146        let string_schema: StringSchema = StringSchema::default();
147        let context = Context::default();
148        let result = string_schema.validate(&context, marked_yaml);
149        assert!(result.is_ok());
150        assert!(context.has_errors());
151    }
152}