Skip to main content

yaml_schema/schemas/
all_of.rs

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