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 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 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
140pub(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}