Skip to main content

yaml_schema/schemas/
all_of.rs

1use log::debug;
2
3use saphyr::AnnotatedMapping;
4use saphyr::MarkedYaml;
5use saphyr::YamlData;
6
7use crate::Context;
8use crate::Error;
9use crate::Result;
10use crate::Validator;
11use crate::YamlSchema;
12use crate::loader;
13use crate::utils::format_vec;
14
15/// The `allOf` schema is a schema that matches if all of the schemas in the `allOf` array match.
16/// The schemas are tried in order, and the first match is used. If no match is found, an error is added
17/// to the context.
18#[derive(Debug, Default, PartialEq)]
19pub struct AllOfSchema {
20    pub all_of: Vec<YamlSchema>,
21}
22
23impl std::fmt::Display for AllOfSchema {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "allOf:{}", format_vec(&self.all_of))
26    }
27}
28
29impl<'r> TryFrom<&MarkedYaml<'r>> for AllOfSchema {
30    type Error = crate::Error;
31
32    fn try_from(value: &MarkedYaml<'r>) -> Result<Self> {
33        if let YamlData::Mapping(mapping) = &value.data {
34            AllOfSchema::try_from(mapping)
35        } else {
36            Err(expected_mapping!(value))
37        }
38    }
39}
40
41impl<'r> TryFrom<&AnnotatedMapping<'r, MarkedYaml<'r>>> for AllOfSchema {
42    type Error = crate::Error;
43
44    fn try_from(mapping: &AnnotatedMapping<'r, MarkedYaml<'r>>) -> crate::Result<Self> {
45        let all_of = match mapping.get(&MarkedYaml::value_from_str("allOf")) {
46            Some(value) => loader::load_array_of_schemas_marked(value)?,
47            None => {
48                debug!("[allOf] No `allOf` key found!");
49                Vec::new()
50            }
51        };
52        Ok(AllOfSchema { all_of })
53    }
54}
55
56impl Validator for AllOfSchema {
57    fn validate(&self, context: &Context, value: &saphyr::MarkedYaml) -> Result<()> {
58        let all_of_is_valid = validate_all_of(&self.all_of, context, value)?;
59        debug!("[AllOf#validate] all_of_is_valid: {all_of_is_valid}");
60        if !all_of_is_valid {
61            debug!("[AllOf#validate] Not all of the schemas in `allOf` matched!");
62            context.add_error(value, "Not all of the schemas in `allOf` matched!");
63            fail_fast!(context);
64        }
65        Ok(())
66    }
67}
68
69pub fn validate_all_of(
70    schemas: &[YamlSchema],
71    context: &Context,
72    value: &saphyr::MarkedYaml,
73) -> Result<bool> {
74    for schema in schemas {
75        debug!("[AllOf#validate_all_of] Validating value: {value:?} against schema: {schema:?}");
76        // We can short circuit as soon as any sub schema fails to validate
77        let sub_context = context.get_sub_context();
78        let sub_result = schema.validate(&sub_context, value);
79        match sub_result {
80            Ok(()) => {
81                debug!("[AllOf#validate_all_of] schema {schema:?} validated");
82                debug!(
83                    "[AllOf#validate_all_of] sub_context.has_errors(): {}",
84                    sub_context.has_errors()
85                );
86                if sub_context.has_errors() {
87                    return Ok(false);
88                }
89            }
90            Err(Error::FailFast) => return Ok(false),
91            Err(e) => return Err(e),
92        }
93    }
94    // If we get here, then all of the schemas matched
95    Ok(true)
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use crate::schemas::StringSchema;
102    use saphyr::LoadableYamlNode;
103
104    fn create_test_schema() -> AllOfSchema {
105        AllOfSchema {
106            all_of: vec![
107                StringSchema::builder().min_length(1).build().into(),
108                StringSchema::builder().max_length(5).build().into(),
109            ],
110        }
111    }
112
113    #[test]
114    fn test_validate_all_of() {
115        let schema = create_test_schema();
116        let context = Context::default();
117        let docs = MarkedYaml::load_from_str("valid").unwrap();
118        let value = docs.first().unwrap();
119
120        let result = schema.validate(&context, value);
121
122        assert!(result.is_ok());
123        assert!(!context.has_errors());
124    }
125
126    #[test]
127    fn test_validate_all_of_invalid() {
128        let schema = create_test_schema();
129        let context = Context::default();
130        let docs = MarkedYaml::load_from_str("too long").unwrap();
131        let value = docs.first().unwrap();
132
133        let result = schema.validate(&context, value);
134
135        assert!(result.is_ok());
136        assert!(context.has_errors());
137        let errors = context.errors.borrow();
138        let error = errors.first().unwrap();
139        assert_eq!("Not all of the schemas in `allOf` matched!", error.error);
140    }
141}