Skip to main content

jsonschema/keywords/
any_of.rs

1use crate::{
2    compiler,
3    error::{error, no_error, ErrorIterator, ValidationError},
4    node::SchemaNode,
5    paths::{LazyLocation, Location, RefTracker},
6    types::JsonType,
7    validator::{EvaluationResult, Validate, ValidationContext},
8};
9use serde_json::{Map, Value};
10
11use super::CompilationResult;
12
13pub(crate) struct AnyOfValidator {
14    schemas: Vec<SchemaNode>,
15    location: Location,
16}
17
18impl AnyOfValidator {
19    #[inline]
20    pub(crate) fn compile<'a>(ctx: &compiler::Context, schema: &'a Value) -> CompilationResult<'a> {
21        if let Value::Array(items) = schema {
22            let ctx = ctx.new_at_location("anyOf");
23            let mut schemas = Vec::with_capacity(items.len());
24            for (idx, item) in items.iter().enumerate() {
25                let ctx = ctx.new_at_location(idx);
26                let node = compiler::compile(&ctx, ctx.as_resource_ref(item))?;
27                schemas.push(node);
28            }
29            Ok(Box::new(AnyOfValidator {
30                schemas,
31                location: ctx.location().clone(),
32            }))
33        } else {
34            let location = ctx.location().join("anyOf");
35            Err(ValidationError::single_type_error(
36                location.clone(),
37                location,
38                Location::new(),
39                schema,
40                JsonType::Array,
41            ))
42        }
43    }
44}
45
46impl Validate for AnyOfValidator {
47    fn is_valid(&self, instance: &Value, ctx: &mut ValidationContext) -> bool {
48        self.schemas.iter().any(|s| s.is_valid(instance, ctx))
49    }
50
51    fn validate<'i>(
52        &self,
53        instance: &'i Value,
54        location: &LazyLocation,
55        tracker: Option<&RefTracker>,
56        ctx: &mut ValidationContext,
57    ) -> Result<(), ValidationError<'i>> {
58        if self.is_valid(instance, ctx) {
59            Ok(())
60        } else {
61            Err(ValidationError::any_of(
62                self.location.clone(),
63                crate::paths::capture_evaluation_path(tracker, &self.location),
64                location.into(),
65                instance,
66                self.schemas
67                    .iter()
68                    .map(|schema| {
69                        schema
70                            .iter_errors(instance, location, tracker, ctx)
71                            .collect()
72                    })
73                    .collect(),
74            ))
75        }
76    }
77
78    fn iter_errors<'i>(
79        &self,
80        instance: &'i Value,
81        location: &LazyLocation,
82        tracker: Option<&RefTracker>,
83        ctx: &mut ValidationContext,
84    ) -> ErrorIterator<'i> {
85        if self.is_valid(instance, ctx) {
86            no_error()
87        } else {
88            error(ValidationError::any_of(
89                self.location.clone(),
90                crate::paths::capture_evaluation_path(tracker, &self.location),
91                location.into(),
92                instance,
93                self.schemas
94                    .iter()
95                    .map(|schema| {
96                        schema
97                            .iter_errors(instance, location, tracker, ctx)
98                            .collect()
99                    })
100                    .collect(),
101            ))
102        }
103    }
104
105    fn evaluate(
106        &self,
107        instance: &Value,
108        location: &LazyLocation,
109        tracker: Option<&RefTracker>,
110        ctx: &mut ValidationContext,
111    ) -> EvaluationResult {
112        // Per spec ยง10.2.1.2, annotations must be collected from ALL valid branches.
113        // First detect all valid branches cheaply, then evaluate only those branches to avoid
114        // constructing dropped error trees for invalid branches in the common case.
115        let valid_indices: Vec<_> = self
116            .schemas
117            .iter()
118            .enumerate()
119            .filter_map(|(idx, node)| node.is_valid(instance, ctx).then_some(idx))
120            .collect();
121
122        if valid_indices.is_empty() {
123            // No valid schemas - evaluate all for error output.
124            let failures: Vec<_> = self
125                .schemas
126                .iter()
127                .map(|node| node.evaluate_instance(instance, location, tracker, ctx))
128                .collect();
129            EvaluationResult::from_children(failures)
130        } else {
131            let valid_results: Vec<_> = valid_indices
132                .into_iter()
133                .map(|idx| self.schemas[idx].evaluate_instance(instance, location, tracker, ctx))
134                .collect();
135            EvaluationResult::from_children(valid_results)
136        }
137    }
138}
139
140/// Optimized validator for `anyOf` with a single subschema.
141pub(crate) struct SingleAnyOfValidator {
142    node: SchemaNode,
143    location: Location,
144}
145
146impl SingleAnyOfValidator {
147    #[inline]
148    pub(crate) fn compile<'a>(ctx: &compiler::Context, schema: &'a Value) -> CompilationResult<'a> {
149        let any_of_ctx = ctx.new_at_location("anyOf");
150        let item_ctx = any_of_ctx.new_at_location(0);
151        let node = compiler::compile(&item_ctx, item_ctx.as_resource_ref(schema))?;
152        Ok(Box::new(SingleAnyOfValidator {
153            node,
154            location: any_of_ctx.location().clone(),
155        }))
156    }
157}
158
159impl Validate for SingleAnyOfValidator {
160    fn is_valid(&self, instance: &Value, ctx: &mut ValidationContext) -> bool {
161        self.node.is_valid(instance, ctx)
162    }
163
164    fn validate<'i>(
165        &self,
166        instance: &'i Value,
167        location: &LazyLocation,
168        tracker: Option<&RefTracker>,
169        ctx: &mut ValidationContext,
170    ) -> Result<(), ValidationError<'i>> {
171        if self.node.is_valid(instance, ctx) {
172            Ok(())
173        } else {
174            Err(ValidationError::any_of(
175                self.location.clone(),
176                crate::paths::capture_evaluation_path(tracker, &self.location),
177                location.into(),
178                instance,
179                vec![self
180                    .node
181                    .iter_errors(instance, location, tracker, ctx)
182                    .collect()],
183            ))
184        }
185    }
186
187    fn iter_errors<'i>(
188        &self,
189        instance: &'i Value,
190        location: &LazyLocation,
191        tracker: Option<&RefTracker>,
192        ctx: &mut ValidationContext,
193    ) -> ErrorIterator<'i> {
194        if self.node.is_valid(instance, ctx) {
195            no_error()
196        } else {
197            error(ValidationError::any_of(
198                self.location.clone(),
199                crate::paths::capture_evaluation_path(tracker, &self.location),
200                location.into(),
201                instance,
202                vec![self
203                    .node
204                    .iter_errors(instance, location, tracker, ctx)
205                    .collect()],
206            ))
207        }
208    }
209
210    fn evaluate(
211        &self,
212        instance: &Value,
213        location: &LazyLocation,
214        tracker: Option<&RefTracker>,
215        ctx: &mut ValidationContext,
216    ) -> EvaluationResult {
217        EvaluationResult::from(
218            self.node
219                .evaluate_instance(instance, location, tracker, ctx),
220        )
221    }
222}
223
224#[inline]
225pub(crate) fn compile<'a>(
226    ctx: &compiler::Context,
227    _: &'a Map<String, Value>,
228    schema: &'a Value,
229) -> Option<CompilationResult<'a>> {
230    if let Value::Array(items) = schema {
231        match items.as_slice() {
232            [item] => Some(SingleAnyOfValidator::compile(ctx, item)),
233            _ => Some(AnyOfValidator::compile(ctx, schema)),
234        }
235    } else {
236        let location = ctx.location().join("anyOf");
237        Some(Err(ValidationError::single_type_error(
238            location.clone(),
239            location,
240            Location::new(),
241            schema,
242            JsonType::Array,
243        )))
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use crate::tests_util;
250    use serde_json::{json, Value};
251    use test_case::test_case;
252
253    #[test_case(&json!({"anyOf": [{"type": "string"}]}), &json!(1), "/anyOf")]
254    #[test_case(&json!({"anyOf": [{"type": "integer"}, {"type": "string"}]}), &json!({}), "/anyOf")]
255    fn location(schema: &Value, instance: &Value, expected: &str) {
256        tests_util::assert_schema_location(schema, instance, expected);
257    }
258}