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