jsonschema_valid/
validators.rs

1#![allow(non_snake_case)]
2#![allow(clippy::too_many_arguments)]
3
4use serde_json::{json, Map, Value, Value::Array, Value::Bool, Value::Object};
5
6use crate::config::Config;
7use crate::context::Context;
8use crate::error::{make_error, no_error, ErrorIterator, ValidationError};
9use crate::unique;
10use crate::util;
11
12/// The type of the individual validation functions.
13///
14/// # Arguments
15///
16/// * `cfg`: Settings for the current validation run that don't change
17///   during the run.
18/// * `instance`: The part of the JSON document being validated.
19/// * `schema`: The part of the JSON schema that the JSON document is being
20///   validated against.
21/// * `parent_schema`: The parent node of the `schema`.  Used to look up
22///   sibling attributes, such as `if`/`then`/`else`.
23/// * `ref_context`: The context in which to look up `$ref` elements. This is a
24///   stack that is pushed/popped when entering `$ref` contexts.  It is always
25///   the top element in which JSON path references are resolved.
26/// * `errors`: An object to report errors. Depending on the concrete
27///   implementation, this might store all errors in a `Vec`, or it might print
28///   them to a stream immediately.
29///
30/// # Returns
31///
32/// * `Iterator` over `ValidationError`
33pub type Validator<'a> = fn(
34    cfg: &'a Config<'a>,
35    instance: &'a Value,
36    schema: &'a Value,
37    parent_schema: Option<&'a Value>,
38    ref_context: Context<'a>,
39) -> ErrorIterator<'a>;
40
41/// The top-level validation function that performs all of the concrete
42/// validation functions at a given instance/schema pair.
43
44pub fn descend<'a>(
45    cfg: &'a Config<'a>,
46    instance: &'a Value,
47    schema: &'a Value,
48    _parent_schema: Option<&'a Value>,
49    ref_context: Context<'a>,
50) -> ErrorIterator<'a> {
51    match schema {
52        Bool(b) => {
53            if *b {
54                no_error()
55            } else {
56                make_error("false schema always fails", Some(instance), Some(schema))
57            }
58        }
59        Object(schema_object) => {
60            if let (Some(ref_), Some(validator)) =
61                (schema_object.get("$ref"), cfg.get_validator("$ref"))
62            {
63                Box::new(validator(cfg, instance, ref_, Some(schema), ref_context))
64            } else {
65                Box::new(
66                    schema_object
67                        .iter()
68                        .flat_map(move |(k, v)| -> ErrorIterator<'a> {
69                            if let Some(validator) = cfg.get_validator(k) {
70                                Box::new(
71                                    validator(cfg, instance, v, Some(schema), ref_context)
72                                        .map(move |err| err.schema_ctx(k.to_string())),
73                                )
74                            } else {
75                                no_error()
76                            }
77                        }),
78                )
79            }
80        }
81        _ => make_error(
82            "Invalid schema. Must be boolean or object.",
83            None,
84            Some(schema),
85        ),
86    }
87}
88
89// The validation functions below all correspond to individual schema checks
90// defined in the JSON schema specification.
91
92pub fn patternProperties<'a>(
93    cfg: &'a Config<'a>,
94    instance: &'a Value,
95    schema: &'a Value,
96    _parent_schema: Option<&'a Value>,
97    ref_context: Context<'a>,
98) -> ErrorIterator<'a> {
99    if let (Object(instance_object), Object(schema_object)) = (instance, schema) {
100        Box::new(schema_object.iter().flat_map(move |(pattern, subschema)| {
101            if let Ok(re) = regex::Regex::new(pattern) {
102                Box::new(
103                    instance_object
104                        .iter()
105                        .flat_map(move |(k, v)| {
106                            if re.is_match(k) {
107                                Box::new(
108                                    descend(cfg, v, subschema, Some(schema), ref_context)
109                                        .map(move |err| err.instance_ctx(k.clone())),
110                                )
111                            } else {
112                                no_error()
113                            }
114                        })
115                        .map(move |err| err.schema_ctx(pattern.clone())),
116                )
117            } else {
118                no_error()
119            }
120        }))
121    } else {
122        no_error()
123    }
124}
125
126pub fn propertyNames<'a>(
127    cfg: &'a Config<'a>,
128    instance: &'a Value,
129    schema: &'a Value,
130    parent_schema: Option<&'a Value>,
131    ref_context: Context<'a>,
132) -> ErrorIterator<'a> {
133    struct PropertyNameIter<'a> {
134        instance_cursor: Box<dyn Iterator<Item = &'a String> + 'a>,
135        cfg: &'a Config<'a>,
136        schema: &'a Value,
137        parent_schema: Option<&'a Value>,
138        ref_context: Context<'a>,
139        collected_errors: Vec<ValidationError>,
140        error_i: usize,
141    }
142
143    impl<'a> Iterator for PropertyNameIter<'a> {
144        type Item = ValidationError;
145
146        fn next(&mut self) -> Option<Self::Item> {
147            loop {
148                if self.error_i < self.collected_errors.len() {
149                    self.error_i += 1;
150                    return Some(self.collected_errors[self.error_i - 1].clone());
151                } else if let Some(instance) = self.instance_cursor.next() {
152                    let key = Value::String(instance.to_string());
153                    self.collected_errors = descend(
154                        self.cfg,
155                        &key,
156                        self.schema,
157                        self.parent_schema,
158                        self.ref_context,
159                    )
160                    .collect();
161                    self.error_i = 0;
162                } else {
163                    return None;
164                }
165            }
166        }
167    }
168
169    if let Object(instance) = instance {
170        Box::new(PropertyNameIter {
171            instance_cursor: Box::new(instance.keys()),
172            cfg,
173            schema,
174            parent_schema,
175            ref_context,
176            collected_errors: Vec::new(),
177            error_i: 0,
178        })
179    } else {
180        no_error()
181    }
182}
183
184fn find_additional_properties<'a>(
185    instance: &'a Map<String, Value>,
186    schema: &'a Map<String, Value>,
187) -> Box<dyn Iterator<Item = &'a str> + 'a> {
188    let properties = schema.get("properties").and_then(Value::as_object);
189    let pattern_regexes = schema
190        .get("patternProperties")
191        .and_then(Value::as_object)
192        .map(|x| {
193            x.keys()
194                .filter_map(|k| regex::Regex::new(k).ok())
195                .collect::<Vec<regex::Regex>>()
196        });
197    Box::new(
198        instance
199            .keys()
200            .filter(move |&property| {
201                !properties.map_or_else(|| false, |x| x.contains_key(property))
202            })
203            .filter(move |&property| {
204                !pattern_regexes
205                    .as_ref()
206                    .map_or_else(|| false, |x| x.iter().any(|y| y.is_match(property)))
207            })
208            .map(|x| x.as_str()),
209    )
210}
211
212pub fn additionalProperties<'a>(
213    cfg: &'a Config<'a>,
214    instance: &'a Value,
215    schema: &'a Value,
216    parent_schema: Option<&'a Value>,
217    ref_context: Context<'a>,
218) -> ErrorIterator<'a> {
219    if let Object(instance_map) = instance {
220        let extras = parent_schema
221            .and_then(|x| x.as_object())
222            .map(|x| find_additional_properties(instance_map, x));
223
224        if let Some(mut extras) = extras {
225            match schema {
226                Object(_) => {
227                    return Box::new(extras.flat_map(move |extra| {
228                        Box::new(
229                            descend(
230                                cfg,
231                                instance.get(extra).unwrap(),
232                                schema,
233                                parent_schema,
234                                ref_context,
235                            )
236                            .map(move |err| err.instance_ctx(extra.to_string())),
237                        )
238                    }));
239                }
240                Bool(bool) => {
241                    if !bool {
242                        let extra_string = util::format_list(&mut extras);
243                        if !extra_string.is_empty() {
244                            return make_error(
245                                format!(
246                                    "Additional properties are not allowed. Found {}.",
247                                    extra_string
248                                ),
249                                Some(instance),
250                                parent_schema,
251                            );
252                        }
253                    }
254                }
255                _ => {}
256            }
257        }
258    }
259    no_error()
260}
261
262pub fn items<'a>(
263    cfg: &'a Config<'a>,
264    instance: &'a Value,
265    schema: &'a Value,
266    _parent_schema: Option<&'a Value>,
267    ref_context: Context<'a>,
268) -> ErrorIterator<'a> {
269    if let Array(instance) = instance {
270        let items = if cfg.get_draft_number() >= 6 {
271            util::bool_to_object_schema(schema)
272        } else {
273            schema
274        };
275
276        match items {
277            Object(_) => Box::new(instance.iter().enumerate().flat_map(move |(index, item)| {
278                Box::new(
279                    descend(cfg, item, items, Some(schema), ref_context)
280                        .map(move |err| err.instance_ctx(index.to_string())),
281                )
282            })),
283            Array(items) => Box::new(instance.iter().enumerate().zip(items.iter()).flat_map(
284                move |((index, item), subschema)| {
285                    Box::new(
286                        descend(cfg, item, subschema, Some(schema), ref_context)
287                            .map(move |err| err.add_ctx(index.to_string(), index.to_string())),
288                    )
289                },
290            )),
291            _ => no_error(),
292        }
293    } else {
294        no_error()
295    }
296}
297
298pub fn additionalItems<'a>(
299    cfg: &'a Config<'a>,
300    instance: &'a Value,
301    schema: &'a Value,
302    parent_schema: Option<&'a Value>,
303    ref_context: Context<'a>,
304) -> ErrorIterator<'a> {
305    if let Some(parent_schema) = parent_schema {
306        if let (Array(instance_array), Some(Array(items))) = (instance, parent_schema.get("items"))
307        {
308            match schema {
309                Object(_) => {
310                    return Box::new(
311                        instance_array
312                            .iter()
313                            .enumerate()
314                            .skip(items.len())
315                            .flat_map(move |(index, item)| {
316                                Box::new(
317                                    descend(cfg, item, schema, Some(parent_schema), ref_context)
318                                        .map(move |err| err.instance_ctx(index.to_string())),
319                                )
320                            }),
321                    )
322                }
323                Bool(b) => {
324                    if !b && instance_array.len() > items.len() {
325                        return make_error(
326                            "Additional items are not allowed.",
327                            Some(instance),
328                            Some(parent_schema),
329                        );
330                    }
331                }
332                _ => {}
333            }
334        }
335    }
336    no_error()
337}
338
339pub fn const_<'a>(
340    _cfg: &'a Config<'a>,
341    instance: &'a Value,
342    schema: &'a Value,
343    _parent_schema: Option<&'a Value>,
344    _ref_context: Context<'a>,
345) -> ErrorIterator<'a> {
346    if !util::json_equal(instance, schema) {
347        make_error("const doesn't match.", Some(instance), Some(schema))
348    } else {
349        no_error()
350    }
351}
352
353pub fn contains<'a>(
354    cfg: &'a Config<'a>,
355    instance: &'a Value,
356    schema: &'a Value,
357    parent_schema: Option<&'a Value>,
358    ref_context: Context<'a>,
359) -> ErrorIterator<'a> {
360    if let Array(instance_array) = instance {
361        for item in instance_array {
362            if descend(cfg, item, schema, parent_schema, ref_context)
363                .next()
364                .is_none()
365            {
366                return no_error();
367            }
368        }
369        return make_error(
370            "No items in array valid under the given schema.",
371            Some(instance),
372            Some(schema),
373        );
374    }
375    no_error()
376}
377
378pub fn exclusiveMinimum<'a>(
379    _cfg: &'a Config<'a>,
380    instance: &'a Value,
381    schema: &'a Value,
382    _parent_schema: Option<&'a Value>,
383    _ref_context: Context<'a>,
384) -> ErrorIterator<'a> {
385    if let (Value::Number(instance_number), Value::Number(schema_number)) = (instance, schema) {
386        if instance_number.as_f64() <= schema_number.as_f64() {
387            return make_error(
388                format!("{} <= exclusiveMinimum {}", instance_number, schema_number),
389                Some(instance),
390                Some(schema),
391            );
392        }
393    }
394    no_error()
395}
396
397pub fn exclusiveMaximum<'a>(
398    _cfg: &'a Config<'a>,
399    instance: &'a Value,
400    schema: &'a Value,
401    _parent_schema: Option<&'a Value>,
402    _ref_context: Context<'a>,
403) -> ErrorIterator<'a> {
404    if let (Value::Number(instance_number), Value::Number(schema_number)) = (instance, schema) {
405        if instance_number.as_f64() >= schema_number.as_f64() {
406            return make_error(
407                format!("{} >= exclusiveMaximum {}", instance_number, schema_number),
408                Some(instance),
409                Some(schema),
410            );
411        }
412    }
413    no_error()
414}
415
416pub fn minimum_draft4<'a>(
417    _cfg: &'a Config<'a>,
418    instance: &'a Value,
419    schema: &'a Value,
420    parent_schema: Option<&'a Value>,
421    _ref_context: Context<'a>,
422) -> ErrorIterator<'a> {
423    if let (Value::Number(instance_number), Value::Number(minimum)) = (instance, schema) {
424        if parent_schema
425            .and_then(|x| x.get("exclusiveMinimum"))
426            .and_then(Value::as_bool)
427            .unwrap_or(false)
428        {
429            if instance_number.as_f64() <= minimum.as_f64() {
430                return make_error(
431                    format!("{} <= exclusiveMinimum {}", instance_number, minimum),
432                    Some(instance),
433                    Some(schema),
434                );
435            }
436        } else if instance_number.as_f64() < minimum.as_f64() {
437            return make_error(
438                format!("{} <= minimum {}", instance_number, minimum),
439                Some(instance),
440                Some(schema),
441            );
442        }
443    }
444    no_error()
445}
446
447pub fn minimum<'a>(
448    _cfg: &'a Config<'a>,
449    instance: &'a Value,
450    schema: &'a Value,
451    _parent_schema: Option<&'a Value>,
452    _ref_context: Context<'a>,
453) -> ErrorIterator<'a> {
454    if let (Value::Number(instance_number), Value::Number(schema_number)) = (instance, schema) {
455        if instance.as_f64() < schema_number.as_f64() {
456            return make_error(
457                format!("{} < minimum {}", instance_number, schema_number),
458                Some(instance),
459                Some(schema),
460            );
461        }
462    }
463    no_error()
464}
465
466pub fn maximum_draft4<'a>(
467    _cfg: &'a Config<'a>,
468    instance: &'a Value,
469    schema: &'a Value,
470    parent_schema: Option<&'a Value>,
471    _ref_context: Context<'a>,
472) -> ErrorIterator<'a> {
473    if let (Value::Number(instance_number), Value::Number(maximum)) = (instance, schema) {
474        if parent_schema
475            .and_then(|x| x.get("exclusiveMaximum"))
476            .and_then(Value::as_bool)
477            .unwrap_or(false)
478        {
479            if instance_number.as_f64() >= maximum.as_f64() {
480                return make_error(
481                    format!("{} >= exclusiveMaximum {}", instance_number, maximum),
482                    Some(instance),
483                    Some(schema),
484                );
485            }
486        } else if instance_number.as_f64() > maximum.as_f64() {
487            return make_error(
488                format!("{} > maximum {}", instance_number, maximum),
489                Some(instance),
490                Some(schema),
491            );
492        }
493    }
494    no_error()
495}
496
497pub fn maximum<'a>(
498    _cfg: &'a Config<'a>,
499    instance: &'a Value,
500    schema: &'a Value,
501    _parent_schema: Option<&'a Value>,
502    _ref_context: Context<'a>,
503) -> ErrorIterator<'a> {
504    if let (Value::Number(instance_number), Value::Number(maximum)) = (instance, schema) {
505        if instance_number.as_f64() > maximum.as_f64() {
506            return make_error(
507                format!("{} > maximum {}", instance_number, maximum),
508                Some(instance),
509                Some(schema),
510            );
511        }
512    }
513    no_error()
514}
515
516#[allow(clippy::float_cmp)]
517pub fn multipleOf<'a>(
518    _cfg: &'a Config<'a>,
519    instance: &'a Value,
520    schema: &'a Value,
521    _parent_schema: Option<&'a Value>,
522    _ref_context: Context<'a>,
523) -> ErrorIterator<'a> {
524    if let (Value::Number(instance_number), Value::Number(schema_number)) = (instance, schema) {
525        let failed = if schema_number.is_f64() {
526            let quotient = instance_number.as_f64().unwrap() / schema_number.as_f64().unwrap();
527            quotient.trunc() != quotient
528        } else if schema_number.is_u64() {
529            (instance_number.as_u64().unwrap() % schema_number.as_u64().unwrap()) != 0
530        } else {
531            (instance_number.as_i64().unwrap() % schema_number.as_i64().unwrap()) != 0
532        };
533        if failed {
534            return make_error(
535                format!("{} not multipleOf {}", instance_number, schema_number),
536                Some(instance),
537                Some(schema),
538            );
539        }
540    }
541    no_error()
542}
543
544pub fn minItems<'a>(
545    _cfg: &'a Config<'a>,
546    instance: &'a Value,
547    schema: &'a Value,
548    _parent_schema: Option<&'a Value>,
549    _ref_context: Context<'a>,
550) -> ErrorIterator<'a> {
551    if let (Array(instance_array), Value::Number(schema_number)) = (instance, schema) {
552        if instance_array.len() < schema_number.as_u64().unwrap() as usize {
553            return make_error(
554                format!("{} < minItems {}", instance_array.len(), schema_number),
555                Some(instance),
556                Some(schema),
557            );
558        }
559    }
560    no_error()
561}
562
563pub fn maxItems<'a>(
564    _cfg: &'a Config<'a>,
565    instance: &'a Value,
566    schema: &'a Value,
567    _parent_schema: Option<&'a Value>,
568    _ref_context: Context<'a>,
569) -> ErrorIterator<'a> {
570    if let (Array(instance_array), Value::Number(schema_number)) = (instance, schema) {
571        if instance_array.len() > schema_number.as_u64().unwrap() as usize {
572            return make_error(
573                format!("{} > maxItems {}", instance_array.len(), schema_number),
574                Some(instance),
575                Some(schema),
576            );
577        }
578    }
579    no_error()
580}
581
582pub fn uniqueItems<'a>(
583    _cfg: &'a Config<'a>,
584    instance: &'a Value,
585    schema: &'a Value,
586    _parent_schema: Option<&'a Value>,
587    _ref_context: Context<'a>,
588) -> ErrorIterator<'a> {
589    if let (Array(instance_array), Bool(schema)) = (instance, schema) {
590        if *schema && !unique::has_unique_elements(&mut instance_array.iter()) {
591            return make_error("Items are not unique", Some(instance), None);
592        }
593    }
594    no_error()
595}
596
597pub fn pattern<'a>(
598    _cfg: &'a Config<'a>,
599    instance: &'a Value,
600    schema: &'a Value,
601    _parent_schema: Option<&'a Value>,
602    _ref_context: Context<'a>,
603) -> ErrorIterator<'a> {
604    if let (Value::String(instance_string), Value::String(schema_string)) = (instance, schema) {
605        if let Ok(re) = regex::Regex::new(schema_string) {
606            if !re.is_match(instance_string) {
607                return make_error("Does not match pattern.", Some(instance), Some(schema));
608            }
609        } else {
610            return make_error("Invalid regex.", None, Some(schema));
611        }
612    }
613    no_error()
614}
615
616pub fn format<'a>(
617    cfg: &'a Config<'a>,
618    instance: &'a Value,
619    schema: &'a Value,
620    _parent_schema: Option<&'a Value>,
621    _ref_context: Context<'a>,
622) -> ErrorIterator<'a> {
623    if let (Value::String(instance_string), Value::String(schema_string)) = (instance, schema) {
624        if let Some(checker) = cfg.get_format_checker(schema_string) {
625            if !checker(cfg, instance_string) {
626                return make_error("Invalid for format.", Some(instance), Some(schema));
627            }
628        }
629    }
630    no_error()
631}
632
633pub fn minLength<'a>(
634    _cfg: &'a Config<'a>,
635    instance: &'a Value,
636    schema: &'a Value,
637    _parent_schema: Option<&'a Value>,
638    _ref_context: Context<'a>,
639) -> ErrorIterator<'a> {
640    if let (Value::String(instance_string), Value::Number(schema_number)) = (instance, schema) {
641        let count = instance_string.chars().count();
642        if count < schema_number.as_u64().unwrap() as usize {
643            return make_error(
644                format!("{} < minLength {}", count, schema_number),
645                Some(instance),
646                Some(schema),
647            );
648        }
649    }
650    no_error()
651}
652
653pub fn maxLength<'a>(
654    _cfg: &'a Config<'a>,
655    instance: &'a Value,
656    schema: &'a Value,
657    _parent_schema: Option<&'a Value>,
658    _ref_context: Context<'a>,
659) -> ErrorIterator<'a> {
660    if let (Value::String(instance_string), Value::Number(schema_number)) = (instance, schema) {
661        let count = instance_string.chars().count();
662        if count > schema_number.as_u64().unwrap() as usize {
663            return make_error(
664                format!("{} < maxLength {}", count, schema_number),
665                Some(instance),
666                Some(schema),
667            );
668        }
669    }
670    no_error()
671}
672
673pub fn dependencies<'a>(
674    cfg: &'a Config<'a>,
675    instance: &'a Value,
676    schema: &'a Value,
677    _parent_schema: Option<&'a Value>,
678    ref_context: Context<'a>,
679) -> ErrorIterator<'a> {
680    if let (Object(instance_object), Object(schema_object)) = (instance, schema) {
681        Box::new(
682            schema_object
683                .iter()
684                .filter(move |(property, _dependency)| {
685                    instance_object.contains_key(property.as_str())
686                })
687                .flat_map(move |(property, dependency)| -> ErrorIterator<'a> {
688                    let dep = util::bool_to_object_schema(dependency);
689                    if let Object(_) = dep {
690                        return Box::new(
691                            descend(cfg, instance, dep, Some(schema), ref_context)
692                                .map(move |err| err.schema_ctx(property.clone())),
693                        );
694                    } else {
695                        for dep0 in util::iter_or_once(dep) {
696                            if let Value::String(key) = dep0 {
697                                if !instance_object.contains_key(key) {
698                                    return make_error(
699                                        "Invalid dependencies",
700                                        Some(instance),
701                                        Some(schema),
702                                    );
703                                }
704                            }
705                        }
706                    }
707                    no_error()
708                }),
709        )
710    } else {
711        no_error()
712    }
713}
714
715pub fn enum_<'a>(
716    _cfg: &'a Config<'a>,
717    instance: &'a Value,
718    schema: &'a Value,
719    _parent_schema: Option<&'a Value>,
720    _ref_context: Context<'a>,
721) -> ErrorIterator<'a> {
722    if let Array(enums) = schema {
723        if !enums.iter().any(|val| util::json_equal(val, instance)) {
724            return make_error("Value is not in enum.", Some(instance), Some(schema));
725        }
726    }
727    no_error()
728}
729
730#[allow(clippy::float_cmp)]
731fn single_type(instance: &Value, schema: &Value) -> bool {
732    if let Value::String(typename) = schema {
733        return match typename.as_ref() {
734            "array" => matches!(instance, Array(_)),
735            "object" => matches!(instance, Object(_)),
736            "null" => matches!(instance, Value::Null),
737            "number" => matches!(instance, Value::Number(_)),
738            "string" => matches!(instance, Value::String(_)),
739            "integer" => {
740                if let Value::Number(number) = instance {
741                    number.is_i64()
742                        || number.is_u64()
743                        || (number.is_f64()
744                            && number.as_f64().unwrap().trunc() == number.as_f64().unwrap())
745                } else {
746                    false
747                }
748            }
749            "boolean" => matches!(instance, Bool(_)),
750            _ => true,
751        };
752    }
753    true
754}
755
756pub fn type_<'a>(
757    _cfg: &'a Config<'a>,
758    instance: &'a Value,
759    schema: &'a Value,
760    parent_schema: Option<&'a Value>,
761    _ref_context: Context<'a>,
762) -> ErrorIterator<'a> {
763    if !util::iter_or_once(schema).any(|x| single_type(instance, x)) {
764        return make_error("Invalid type.", Some(instance), parent_schema);
765    }
766    no_error()
767}
768
769pub fn properties<'a>(
770    cfg: &'a Config<'a>,
771    instance: &'a Value,
772    schema: &'a Value,
773    _parent_schema: Option<&'a Value>,
774    ref_context: Context<'a>,
775) -> ErrorIterator<'a> {
776    if let (Object(instance_object), Object(schema_object)) = (instance, schema) {
777        Box::new(schema_object.iter().flat_map(move |(property, subschema)| {
778            if let Some(property_value) = instance_object.get(property) {
779                Box::new(
780                    descend(cfg, property_value, subschema, Some(schema), ref_context)
781                        .map(move |err| err.add_ctx(property.clone(), property.clone())),
782                )
783            } else {
784                no_error()
785            }
786        }))
787    } else {
788        no_error()
789    }
790}
791
792pub fn required<'a>(
793    _cfg: &'a Config<'a>,
794    instance: &'a Value,
795    schema: &'a Value,
796    _parent_schema: Option<&'a Value>,
797    _ref_context: Context<'a>,
798) -> ErrorIterator<'a> {
799    if let (Object(instance_object), Array(schema_array)) = (instance, schema) {
800        let missing_properties: Vec<&str> = schema_array
801            .iter()
802            .filter_map(Value::as_str)
803            .filter(|&x| !instance_object.contains_key(&x.to_string()))
804            .collect();
805
806        if !missing_properties.is_empty() {
807            return make_error(
808                format!(
809                    "Required properties {} are missing",
810                    util::format_list(&mut missing_properties.iter().copied())
811                ),
812                Some(instance),
813                Some(schema),
814            );
815        }
816    }
817    no_error()
818}
819
820pub fn minProperties<'a>(
821    _cfg: &'a Config<'a>,
822    instance: &'a Value,
823    schema: &'a Value,
824    _parent_schema: Option<&'a Value>,
825    _ref_context: Context<'a>,
826) -> ErrorIterator<'a> {
827    if let (Object(instance_object), Value::Number(schema_number)) = (instance, schema) {
828        if instance_object.len() < schema_number.as_u64().unwrap() as usize {
829            return make_error(
830                format!(
831                    "{} < minProperties {}",
832                    instance_object.len(),
833                    schema_number
834                ),
835                Some(instance),
836                Some(schema),
837            );
838        }
839    }
840    no_error()
841}
842
843pub fn maxProperties<'a>(
844    _cfg: &'a Config<'a>,
845    instance: &'a Value,
846    schema: &'a Value,
847    _parent_schema: Option<&'a Value>,
848    _ref_context: Context<'a>,
849) -> ErrorIterator<'a> {
850    if let (Object(instance_object), Value::Number(schema_number)) = (instance, schema) {
851        if instance_object.len() > schema_number.as_u64().unwrap() as usize {
852            return make_error(
853                format!(
854                    "{} > maxProperties {}",
855                    instance_object.len(),
856                    schema_number
857                ),
858                Some(instance),
859                Some(schema),
860            );
861        }
862    }
863    no_error()
864}
865
866pub fn allOf<'a>(
867    cfg: &'a Config<'a>,
868    instance: &'a Value,
869    schema: &'a Value,
870    _parent_schema: Option<&'a Value>,
871    ref_context: Context<'a>,
872) -> ErrorIterator<'a> {
873    if let Array(schema_array) = schema {
874        Box::new(
875            schema_array
876                .iter()
877                .enumerate()
878                .flat_map(move |(index, subschema)| {
879                    let subschema0 = if cfg.get_draft_number() >= 6 {
880                        util::bool_to_object_schema(subschema)
881                    } else {
882                        subschema
883                    };
884                    Box::new(
885                        descend(cfg, instance, subschema0, Some(schema), ref_context)
886                            .map(move |err| err.schema_ctx(index.to_string())),
887                    )
888                }),
889        )
890    } else {
891        no_error()
892    }
893}
894
895pub fn anyOf<'a>(
896    cfg: &'a Config<'a>,
897    instance: &'a Value,
898    schema: &'a Value,
899    _parent_schema: Option<&'a Value>,
900    ref_context: Context<'a>,
901) -> ErrorIterator<'a> {
902    if let Array(schema_array) = schema {
903        for subschema in schema_array.iter() {
904            let subschema0 = if cfg.get_draft_number() >= 6 {
905                util::bool_to_object_schema(subschema)
906            } else {
907                subschema
908            };
909            if descend(cfg, instance, subschema0, Some(schema), ref_context)
910                .next()
911                .is_none()
912            {
913                return no_error();
914            }
915        }
916        return make_error("anyOf failed", Some(instance), Some(schema));
917    }
918    no_error()
919}
920
921pub fn oneOf<'a>(
922    cfg: &'a Config<'a>,
923    instance: &'a Value,
924    schema: &'a Value,
925    _parent_schema: Option<&'a Value>,
926    ref_context: Context<'a>,
927) -> ErrorIterator<'a> {
928    if let Array(schema_array) = schema {
929        let mut oneOf = schema_array.iter().enumerate();
930        let mut found_one = false;
931        for (_, subschema) in oneOf.by_ref() {
932            let subschema0 = if cfg.get_draft_number() >= 6 {
933                util::bool_to_object_schema(subschema)
934            } else {
935                subschema
936            };
937            if descend(cfg, instance, subschema0, Some(schema), ref_context)
938                .next()
939                .is_none()
940            {
941                found_one = true;
942                break;
943            }
944        }
945
946        if !found_one {
947            return make_error("nothing matched in oneOf", Some(instance), Some(schema));
948        }
949
950        let mut found_more = false;
951        for (_, subschema) in oneOf.by_ref() {
952            let subschema0 = if cfg.get_draft_number() >= 6 {
953                util::bool_to_object_schema(subschema)
954            } else {
955                subschema
956            };
957            if descend(cfg, instance, subschema0, Some(schema), ref_context)
958                .next()
959                .is_none()
960            {
961                found_more = true;
962                break;
963            }
964        }
965
966        if found_more {
967            return make_error(
968                "More than one matched in oneOf",
969                Some(instance),
970                Some(schema),
971            );
972        }
973    }
974    no_error()
975}
976
977pub fn not<'a>(
978    cfg: &'a Config<'a>,
979    instance: &'a Value,
980    schema: &'a Value,
981    parent_schema: Option<&'a Value>,
982    ref_context: Context<'a>,
983) -> ErrorIterator<'a> {
984    if descend(cfg, instance, schema, parent_schema, ref_context)
985        .next()
986        .is_none()
987    {
988        make_error("not", Some(instance), Some(schema))
989    } else {
990        no_error()
991    }
992}
993
994pub fn ref_<'a>(
995    cfg: &'a Config<'a>,
996    instance: &'a Value,
997    schema: &'a Value,
998    _parent_schema: Option<&'a Value>,
999    ref_context: Context<'a>,
1000) -> ErrorIterator<'a> {
1001    if let Value::String(sref) = schema {
1002        struct RefIter {
1003            collected_errors: Vec<ValidationError>,
1004            error_i: usize,
1005        }
1006
1007        impl Iterator for RefIter {
1008            type Item = ValidationError;
1009
1010            fn next(&mut self) -> Option<Self::Item> {
1011                if self.error_i < self.collected_errors.len() {
1012                    self.error_i += 1;
1013                    Some(self.collected_errors[self.error_i - 1].clone())
1014                } else {
1015                    None
1016                }
1017            }
1018        }
1019
1020        match cfg
1021            .get_resolver()
1022            .resolve_fragment(cfg.draft, sref, &ref_context, cfg.get_schema())
1023        {
1024            Ok((scope, resolved)) => {
1025                let scope_schema = json!({"$id": scope.to_string()});
1026                return Box::new(RefIter {
1027                    collected_errors: descend(
1028                        cfg,
1029                        instance,
1030                        resolved,
1031                        Some(schema),
1032                        ref_context.push(&scope_schema),
1033                    )
1034                    .collect(),
1035                    error_i: 0,
1036                });
1037            }
1038            Err(_err) => {
1039                return make_error(
1040                    format!("Couldn't resolve reference {}", sref),
1041                    Some(instance),
1042                    None,
1043                )
1044            }
1045        }
1046    }
1047    no_error()
1048}
1049
1050pub fn if_<'a>(
1051    cfg: &'a Config<'a>,
1052    instance: &'a Value,
1053    schema: &'a Value,
1054    parent_schema: Option<&'a Value>,
1055    ref_context: Context<'a>,
1056) -> ErrorIterator<'a> {
1057    if descend(cfg, instance, schema, parent_schema, ref_context)
1058        .next()
1059        .is_none()
1060    {
1061        if let Some(then) = parent_schema.and_then(|x| x.get("then")) {
1062            if then.is_object() {
1063                return Box::new(
1064                    descend(cfg, instance, then, Some(schema), ref_context)
1065                        .map(move |err| err.schema_ctx("then".to_string())),
1066                );
1067            }
1068        }
1069    } else if let Some(else_) = parent_schema.and_then(|x| x.get("else")) {
1070        if else_.is_object() {
1071            return Box::new(
1072                descend(cfg, instance, else_, Some(schema), ref_context)
1073                    .map(move |err| err.schema_ctx("else".to_string())),
1074            );
1075        }
1076    }
1077    no_error()
1078}
1079
1080#[cfg(test)]
1081mod tests {
1082    use crate::{schemas, Config};
1083    use serde_json::json;
1084
1085    #[test]
1086    fn test_additional_properties_errors() {
1087        let schema = json!({
1088            "properties": { "foo": { "type": "integer" } },
1089            "additionalProperties": false
1090        });
1091        let instance = json!({
1092            "foo": 42,
1093            "bar": "additional",
1094            "baz": "another additional"
1095        });
1096        let cfg = Config::from_schema(&schema, Some(schemas::Draft::Draft6)).unwrap();
1097        let validation = cfg.validate(&instance);
1098
1099        if let Err(errors) = validation {
1100            for error in errors {
1101                let formatted = format!("{}", error);
1102                println!("{}", formatted);
1103
1104                assert!(error.instance_path == (Vec::<String>::new()));
1105                assert!(error.schema_path == vec!("additionalProperties"));
1106
1107                assert!(formatted
1108                    .contains("Additional properties are not allowed. Found \"bar\", \"baz\"."));
1109                assert!(formatted.contains("At instance path /:"));
1110                assert!(formatted.contains("At schema path /additionalProperties"));
1111            }
1112        }
1113    }
1114}