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 {
19 pub any_of: Vec<YamlSchema>,
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 {
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 {
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 let mut any_ok = false;
74 for schema in schemas {
75 debug!("[AnyOf] Validating value: {marked_yaml:?} against schema: {schema}");
76 let sub_context = context.get_sub_context_fresh_eval();
77 debug!("[AnyOf] context: {context:?}");
78 debug!("[AnyOf] sub_context: {sub_context:?}");
79 match schema.validate(&sub_context, marked_yaml) {
80 Ok(()) | Err(Error::FailFast) => {
81 if sub_context.has_errors() {
82 continue;
83 }
84 debug!("[AnyOf] Schema {schema:?} matched");
85 any_ok = true;
86 if let (Some(p), Some(b)) =
87 (&context.object_evaluated, &sub_context.object_evaluated)
88 {
89 p.extend(&b.snapshot());
90 }
91 if let (Some(pcell), Some(bcell)) =
92 (&context.array_unevaluated, &sub_context.array_unevaluated)
93 {
94 let snap = bcell.borrow().clone();
95 pcell.borrow_mut().merge_from(&snap);
96 }
97 }
98 Err(e) => return Err(e),
99 }
100 }
101 debug!("[AnyOf] any_ok: {any_ok}");
102 Ok(any_ok)
103}
104
105#[cfg(test)]
106mod tests {
107 use saphyr::MarkedYaml;
108
109 use crate::Context;
110 use crate::Validator as _;
111 use crate::loader;
112
113 #[test]
114 fn test_any_of_with_description() {
115 let schema_str = r#"
116 description: A string or a number
117 anyOf:
118 - type: string
119 - type: number
120 "#;
121 let any_of_schema = loader::load_from_str(schema_str).expect("Failed to load schema");
122
123 let value_str = r#""I am a string""#;
125 let value = MarkedYaml::value_from_str(value_str);
126 assert!(value.data.is_string(), "Value should be a string");
127 let context = Context::default();
128 any_of_schema
129 .validate(&context, &value)
130 .expect("Validation failed");
131 assert!(!context.has_errors(), "Should accept string");
132
133 let value_str = "42";
135 let value = MarkedYaml::value_from_str(value_str);
136 assert!(value.data.is_integer(), "Value should be an integer");
137 let context = Context::default();
138 any_of_schema
139 .validate(&context, &value)
140 .expect("Validation failed");
141 assert!(!context.has_errors(), "Should accept number");
142
143 let value_str = "true";
145 let value = MarkedYaml::value_from_str(value_str);
146 assert!(value.data.is_boolean(), "Value should be a boolean");
147 let context = Context::default();
148 any_of_schema
149 .validate(&context, &value)
150 .expect("Validation failed");
151 assert!(context.has_errors(), "Should NOT accept boolean");
152 }
153}