yaml_schema/schemas/
any_of.rs1use log::debug;
2use saphyr::AnnotatedMapping;
3use saphyr::MarkedYaml;
4use saphyr::YamlData;
5
6use crate::Context;
7use crate::Error;
8use crate::Result;
9use crate::Validator;
10use crate::YamlSchema;
11use crate::loader;
12use crate::utils::format_vec;
13
14#[derive(Debug, Default, PartialEq)]
18pub struct AnyOfSchema<'r> {
19 pub any_of: Vec<YamlSchema<'r>>,
20}
21
22impl std::fmt::Display for AnyOfSchema<'_> {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 write!(f, "anyOf:{}", format_vec(&self.any_of))
25 }
26}
27
28impl<'r> TryFrom<&MarkedYaml<'r>> for AnyOfSchema<'r> {
29 type Error = crate::Error;
30
31 fn try_from(value: &MarkedYaml<'r>) -> Result<Self> {
32 if let YamlData::Mapping(mapping) = &value.data {
33 AnyOfSchema::try_from(mapping)
34 } else {
35 Err(expected_mapping!(value))
36 }
37 }
38}
39
40impl<'r> TryFrom<&AnnotatedMapping<'r, MarkedYaml<'r>>> for AnyOfSchema<'r> {
41 type Error = crate::Error;
42
43 fn try_from(mapping: &AnnotatedMapping<'r, MarkedYaml<'r>>) -> crate::Result<Self> {
44 let mut any_of_schema = AnyOfSchema::default();
45 if let Some(value) = mapping.get(&MarkedYaml::value_from_str("anyOf")) {
46 any_of_schema.any_of = loader::load_array_of_schemas_marked(value)?;
47 } else {
48 debug!("[anyOf] No `anyOf` key found!");
49 }
50 Ok(any_of_schema)
51 }
52}
53
54impl Validator for crate::schemas::AnyOfSchema<'_> {
55 fn validate(&self, context: &Context, value: &saphyr::MarkedYaml) -> Result<()> {
56 let any_of_is_valid = validate_any_of(&self.any_of, context, value)?;
57 debug!("any_of_is_valid: {any_of_is_valid}");
58 if !any_of_is_valid {
59 debug!("AnyOf: None of the schemas in `anyOf` matched!");
60 context.add_error(value, "None of the schemas in `anyOf` matched!");
61 fail_fast!(context);
62 }
63 Ok(())
64 }
65}
66
67pub fn validate_any_of(
68 schemas: &[YamlSchema],
69 context: &Context,
70 marked_yaml: &saphyr::MarkedYaml,
71) -> Result<bool> {
72 debug!("[AnyOf] &context: {context:p}");
73 for schema in schemas {
74 debug!("[AnyOf] Validating value: {marked_yaml:?} against schema: {schema}");
75 let sub_context = context.get_sub_context();
79 debug!("[AnyOf] context: {context:?}");
80 debug!("[AnyOf] sub_context: {sub_context:?}");
81 match schema.validate(&sub_context, marked_yaml) {
82 Ok(()) | Err(Error::FailFast) => {
83 if sub_context.has_errors() {
84 continue;
85 }
86 debug!("[AnyOf] Schema {schema:?} matched");
87 return Ok(true);
88 }
89 Err(e) => return Err(e),
90 }
91 }
92 debug!("[AnyOf] None of the schemas matched");
93 Ok(false)
95}
96
97#[cfg(test)]
98mod tests {
99 use saphyr::MarkedYaml;
100
101 use crate::Context;
102 use crate::Validator as _;
103 use crate::loader;
104
105 #[test]
106 fn test_any_of_with_description() {
107 let schema_str = r#"
108 description: A string or a number
109 anyOf:
110 - type: string
111 - type: number
112 "#;
113 let any_of_schema = loader::load_from_str(schema_str).expect("Failed to load schema");
114
115 let value_str = r#""I am a string""#;
117 let value = MarkedYaml::value_from_str(value_str);
118 assert!(value.data.is_string(), "Value should be a string");
119 let context = Context::default();
120 any_of_schema
121 .validate(&context, &value)
122 .expect("Validation failed");
123 assert!(!context.has_errors(), "Should accept string");
124
125 let value_str = "42";
127 let value = MarkedYaml::value_from_str(value_str);
128 assert!(value.data.is_integer(), "Value should be an integer");
129 let context = Context::default();
130 any_of_schema
131 .validate(&context, &value)
132 .expect("Validation failed");
133 assert!(!context.has_errors(), "Should accept number");
134
135 let value_str = "true";
137 let value = MarkedYaml::value_from_str(value_str);
138 assert!(value.data.is_boolean(), "Value should be a boolean");
139 let context = Context::default();
140 any_of_schema
141 .validate(&context, &value)
142 .expect("Validation failed");
143 assert!(context.has_errors(), "Should NOT accept boolean");
144 }
145}