Skip to main content

jsonschema/
error.rs

1//! # Error Handling
2//!
3//! ## Masking Sensitive Data
4//!
5//! When working with sensitive data, you might want to hide actual values from error messages.
6//! The `ValidationError` type provides methods to mask instance values while preserving the error context:
7//!
8//! ```rust
9//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
10//! use serde_json::json;
11//!
12//! let schema = json!({"maxLength": 5});
13//! let instance = json!("sensitive data");
14//! let validator = jsonschema::validator_for(&schema)?;
15//!
16//! if let Err(error) = validator.validate(&instance) {
17//!     // Use default masking (replaces values with "value")
18//!     println!("Masked error: {}", error.masked());
19//!     // Or provide custom placeholder
20//!     println!("Custom masked: {}", error.masked_with("[REDACTED]"));
21//! }
22//! # Ok(())
23//! # }
24//! ```
25//!
26//! The masked error messages will replace instance values with placeholders while maintaining
27//! schema-related information like property names, limits, and types.
28//!
29//! Original error:
30//! ```text
31//! "sensitive data" is longer than 5 characters
32//! ```
33//!
34//! Masked error:
35//! ```text
36//! value is longer than 5 characters
37//! ```
38use crate::{
39    paths::{LazyLocation, Location},
40    types::{JsonType, JsonTypeSet},
41    validator::LazyEvaluationPath,
42};
43use referencing::Uri;
44use serde_json::{Map, Number, Value};
45use std::{
46    borrow::Cow,
47    error,
48    fmt::{self, Formatter, Write},
49    iter::{empty, once},
50    slice,
51    string::FromUtf8Error,
52    sync::Arc,
53    vec,
54};
55
56/// An error that can occur during validation.
57#[derive(Debug)]
58pub struct ValidationError<'a> {
59    repr: Box<ValidationErrorRepr<'a>>,
60}
61
62struct ValidationErrorRepr<'a> {
63    instance: Cow<'a, Value>,
64    kind: ValidationErrorKind,
65    instance_path: Location,
66    /// Canonical schema location without $ref traversals (JSON Schema "keywordLocation")
67    schema_path: Location,
68    /// Dynamic path including $ref traversals.
69    tracker: LazyEvaluationPath,
70    /// Absolute keyword location: full URI including JSON Pointer fragment.
71    /// E.g. `https://example.com/schema.json#/properties/name/type`.
72    /// Set when the schema has a real base URI (via `$id` or `with_base_uri()`).
73    /// `None` for schemas using the internal `json-schema:///` default.
74    absolute_keyword_location: Option<Arc<Uri<String>>>,
75}
76
77impl fmt::Debug for ValidationErrorRepr<'_> {
78    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
79        f.debug_struct("ValidationErrorRepr")
80            .field("instance", &self.instance)
81            .field("kind", &self.kind)
82            .field("instance_path", &self.instance_path)
83            .field("schema_path", &self.schema_path)
84            .field("absolute_keyword_location", &self.absolute_keyword_location)
85            .finish_non_exhaustive()
86    }
87}
88
89/// An iterator over instances of [`ValidationError`] that represent validation error for the
90/// input instance.
91///
92/// # Examples
93///
94/// ```rust
95/// use serde_json::json;
96///
97/// let schema = json!({"maxLength": 5});
98/// let instance = json!("foo");
99/// if let Ok(validator) = jsonschema::validator_for(&schema) {
100///     let errors = validator.iter_errors(&instance);
101///     for error in errors {
102///         println!("Validation error: {}", error)
103///     }
104/// }
105/// ```
106#[doc(hidden)]
107pub trait ValidationErrorIterator<'a>: Iterator<Item = ValidationError<'a>> + Send + Sync {}
108
109impl<'a, T> ValidationErrorIterator<'a> for T where
110    T: Iterator<Item = ValidationError<'a>> + Send + Sync
111{
112}
113
114/// A lazily-evaluated iterator over validation errors.
115///
116/// Use [`into_errors()`](Self::into_errors) to convert into [`ValidationErrors`],
117/// which implements [`std::error::Error`] for integration with error handling libraries.
118pub struct ErrorIterator<'a> {
119    iter: Box<dyn ValidationErrorIterator<'a> + 'a>,
120}
121
122impl<'a> ErrorIterator<'a> {
123    #[inline]
124    pub(crate) fn from_iterator<T>(iterator: T) -> Self
125    where
126        T: ValidationErrorIterator<'a> + 'a,
127    {
128        Self {
129            iter: Box::new(iterator),
130        }
131    }
132
133    /// Collects all errors into [`ValidationErrors`], which implements [`std::error::Error`].
134    #[inline]
135    #[must_use]
136    pub fn into_errors(self) -> ValidationErrors<'a> {
137        ValidationErrors {
138            errors: self.collect(),
139        }
140    }
141}
142
143/// An owned collection of validation errors that implements [`std::error::Error`].
144///
145/// Obtain this by calling [`ErrorIterator::into_errors()`].
146pub struct ValidationErrors<'a> {
147    errors: Vec<ValidationError<'a>>,
148}
149
150impl<'a> ValidationErrors<'a> {
151    #[inline]
152    #[must_use]
153    pub fn len(&self) -> usize {
154        self.errors.len()
155    }
156
157    #[inline]
158    #[must_use]
159    pub fn is_empty(&self) -> bool {
160        self.errors.is_empty()
161    }
162
163    /// Returns the errors as a slice.
164    #[inline]
165    #[must_use]
166    pub fn as_slice(&self) -> &[ValidationError<'a>] {
167        &self.errors
168    }
169
170    #[inline]
171    pub fn iter(&self) -> slice::Iter<'_, ValidationError<'a>> {
172        self.errors.iter()
173    }
174
175    #[inline]
176    pub fn iter_mut(&mut self) -> slice::IterMut<'_, ValidationError<'a>> {
177        self.errors.iter_mut()
178    }
179}
180
181impl fmt::Display for ValidationErrors<'_> {
182    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
183        if self.errors.is_empty() {
184            f.write_str("Validation succeeded")
185        } else {
186            writeln!(f, "Validation errors:")?;
187            for (idx, error) in self.errors.iter().enumerate() {
188                writeln!(f, "{:02}: {error}", idx + 1)?;
189            }
190            Ok(())
191        }
192    }
193}
194
195impl fmt::Debug for ValidationErrors<'_> {
196    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
197        f.debug_struct("ValidationErrors")
198            .field("errors", &self.errors)
199            .finish()
200    }
201}
202
203impl error::Error for ValidationErrors<'_> {}
204
205impl<'a> Iterator for ErrorIterator<'a> {
206    type Item = ValidationError<'a>;
207
208    #[inline]
209    fn next(&mut self) -> Option<Self::Item> {
210        self.iter.as_mut().next()
211    }
212
213    #[inline]
214    fn size_hint(&self) -> (usize, Option<usize>) {
215        self.iter.size_hint()
216    }
217}
218
219impl<'a> IntoIterator for ValidationErrors<'a> {
220    type Item = ValidationError<'a>;
221    type IntoIter = vec::IntoIter<ValidationError<'a>>;
222
223    fn into_iter(self) -> Self::IntoIter {
224        self.errors.into_iter()
225    }
226}
227
228impl<'a, 'b> IntoIterator for &'b ValidationErrors<'a> {
229    type Item = &'b ValidationError<'a>;
230    type IntoIter = slice::Iter<'b, ValidationError<'a>>;
231
232    fn into_iter(self) -> Self::IntoIter {
233        self.errors.iter()
234    }
235}
236
237impl<'a, 'b> IntoIterator for &'b mut ValidationErrors<'a> {
238    type Item = &'b mut ValidationError<'a>;
239    type IntoIter = slice::IterMut<'b, ValidationError<'a>>;
240
241    fn into_iter(self) -> Self::IntoIter {
242        self.errors.iter_mut()
243    }
244}
245
246// Empty iterator means no error happened
247pub(crate) fn no_error<'a>() -> ErrorIterator<'a> {
248    ErrorIterator::from_iterator(empty())
249}
250// A wrapper for one error
251pub(crate) fn error(instance: ValidationError) -> ErrorIterator {
252    ErrorIterator::from_iterator(once(instance))
253}
254
255/// Kinds of errors that may happen during validation
256#[derive(Debug)]
257#[allow(missing_docs)]
258pub enum ValidationErrorKind {
259    /// The input array contain more items than expected.
260    AdditionalItems { limit: usize },
261    /// Unexpected properties.
262    AdditionalProperties { unexpected: Vec<String> },
263    /// The input value is not valid under any of the schemas listed in the 'anyOf' keyword.
264    AnyOf {
265        context: Vec<Vec<ValidationError<'static>>>,
266    },
267    /// Results from a [`fancy_regex::RuntimeError::BacktrackLimitExceeded`] variant when matching
268    BacktrackLimitExceeded { error: fancy_regex::Error },
269    /// The input value doesn't match expected constant.
270    Constant { expected_value: Value },
271    /// The input array doesn't contain items conforming to the specified schema.
272    Contains,
273    /// The input value does not respect the defined contentEncoding
274    ContentEncoding { content_encoding: String },
275    /// The input value does not respect the defined contentMediaType
276    ContentMediaType { content_media_type: String },
277    /// Custom error message for user-defined validation.
278    Custom { keyword: String, message: String },
279    /// The input value doesn't match any of specified options.
280    Enum { options: Value },
281    /// Value is too large.
282    ExclusiveMaximum { limit: Value },
283    /// Value is too small.
284    ExclusiveMinimum { limit: Value },
285    /// Everything is invalid for `false` schema.
286    FalseSchema,
287    /// When the input doesn't match to the specified format.
288    Format { format: String },
289    /// May happen in `contentEncoding` validation if `base64` encoded data is invalid.
290    FromUtf8 { error: FromUtf8Error },
291    /// Too many items in an array.
292    MaxItems { limit: u64 },
293    /// Value is too large.
294    Maximum { limit: Value },
295    /// String is too long.
296    MaxLength { limit: u64 },
297    /// Too many properties in an object.
298    MaxProperties { limit: u64 },
299    /// Too few items in an array.
300    MinItems { limit: u64 },
301    /// Value is too small.
302    Minimum { limit: Value },
303    /// String is too short.
304    MinLength { limit: u64 },
305    /// Not enough properties in an object.
306    MinProperties { limit: u64 },
307    /// When some number is not a multiple of another number.
308    MultipleOf {
309        #[cfg(feature = "arbitrary-precision")]
310        multiple_of: Value,
311        #[cfg(not(feature = "arbitrary-precision"))]
312        multiple_of: f64,
313    },
314    /// Negated schema failed validation.
315    Not { schema: Value },
316    /// The given schema is valid under more than one of the schemas listed in the 'oneOf' keyword.
317    OneOfMultipleValid {
318        context: Vec<Vec<ValidationError<'static>>>,
319    },
320    /// The given schema is not valid under any of the schemas listed in the 'oneOf' keyword.
321    OneOfNotValid {
322        context: Vec<Vec<ValidationError<'static>>>,
323    },
324    /// When the input doesn't match to a pattern.
325    Pattern { pattern: String },
326    /// Object property names are invalid.
327    PropertyNames {
328        error: Box<ValidationError<'static>>,
329    },
330    /// When a required property is missing.
331    Required { property: Value },
332    /// When the input value doesn't match one or multiple required types.
333    Type { kind: TypeKind },
334    /// Unexpected items.
335    UnevaluatedItems { unexpected: Vec<String> },
336    /// Unexpected properties.
337    UnevaluatedProperties { unexpected: Vec<String> },
338    /// When the input array has non-unique elements.
339    UniqueItems,
340    /// Error during schema ref resolution.
341    Referencing(referencing::Error),
342}
343
344impl ValidationErrorKind {
345    #[must_use]
346    pub fn keyword(&self) -> &str {
347        match self {
348            ValidationErrorKind::AdditionalItems { .. } => "additionalItems",
349            ValidationErrorKind::AdditionalProperties { .. } => "additionalProperties",
350            ValidationErrorKind::AnyOf { .. } => "anyOf",
351            ValidationErrorKind::BacktrackLimitExceeded { .. }
352            | ValidationErrorKind::Pattern { .. } => "pattern",
353            ValidationErrorKind::Constant { .. } => "const",
354            ValidationErrorKind::Contains => "contains",
355            ValidationErrorKind::ContentEncoding { .. } | ValidationErrorKind::FromUtf8 { .. } => {
356                "contentEncoding"
357            }
358            ValidationErrorKind::ContentMediaType { .. } => "contentMediaType",
359            ValidationErrorKind::Custom { keyword, .. } => keyword,
360            ValidationErrorKind::Enum { .. } => "enum",
361            ValidationErrorKind::ExclusiveMaximum { .. } => "exclusiveMaximum",
362            ValidationErrorKind::ExclusiveMinimum { .. } => "exclusiveMinimum",
363            ValidationErrorKind::FalseSchema => "falseSchema",
364            ValidationErrorKind::Format { .. } => "format",
365            ValidationErrorKind::MaxItems { .. } => "maxItems",
366            ValidationErrorKind::Maximum { .. } => "maximum",
367            ValidationErrorKind::MaxLength { .. } => "maxLength",
368            ValidationErrorKind::MaxProperties { .. } => "maxProperties",
369            ValidationErrorKind::MinItems { .. } => "minItems",
370            ValidationErrorKind::Minimum { .. } => "minimum",
371            ValidationErrorKind::MinLength { .. } => "minLength",
372            ValidationErrorKind::MinProperties { .. } => "minProperties",
373            ValidationErrorKind::MultipleOf { .. } => "multipleOf",
374            ValidationErrorKind::Not { .. } => "not",
375            ValidationErrorKind::OneOfMultipleValid { .. }
376            | ValidationErrorKind::OneOfNotValid { .. } => "oneOf",
377            ValidationErrorKind::PropertyNames { .. } => "propertyNames",
378            ValidationErrorKind::Required { .. } => "required",
379            ValidationErrorKind::Type { .. } => "type",
380            ValidationErrorKind::UnevaluatedItems { .. } => "unevaluatedItems",
381            ValidationErrorKind::UnevaluatedProperties { .. } => "unevaluatedProperties",
382            ValidationErrorKind::UniqueItems => "uniqueItems",
383            ValidationErrorKind::Referencing(_) => "$ref",
384        }
385    }
386}
387
388#[derive(Debug)]
389#[allow(missing_docs)]
390pub enum TypeKind {
391    Single(JsonType),
392    Multiple(JsonTypeSet),
393}
394
395/// Owned parts returned by [`ValidationError::into_parts`].
396#[derive(Debug)]
397#[non_exhaustive]
398pub struct ValidationErrorParts<'a> {
399    pub instance: Cow<'a, Value>,
400    pub kind: ValidationErrorKind,
401    pub instance_path: Location,
402    pub schema_path: Location,
403    pub evaluation_path: Location,
404    pub absolute_keyword_location: Option<Arc<Uri<String>>>,
405}
406
407/// Shortcuts for creation of specific error kinds.
408impl<'a> ValidationError<'a> {
409    /// Creates a new validation error from parts.
410    #[inline]
411    #[must_use]
412    pub(crate) fn new(
413        instance: Cow<'a, Value>,
414        kind: ValidationErrorKind,
415        instance_path: Location,
416        schema_path: Location,
417        tracker: impl Into<LazyEvaluationPath>,
418    ) -> Self {
419        Self {
420            repr: Box::new(ValidationErrorRepr {
421                instance,
422                kind,
423                instance_path,
424                schema_path,
425                tracker: tracker.into(),
426                absolute_keyword_location: None,
427            }),
428        }
429    }
430
431    /// Returns a reference to the instance that failed validation.
432    #[inline]
433    #[must_use]
434    pub fn instance(&self) -> &Cow<'a, Value> {
435        &self.repr.instance
436    }
437
438    /// Returns the kind of validation error.
439    #[inline]
440    #[must_use]
441    pub fn kind(&self) -> &ValidationErrorKind {
442        &self.repr.kind
443    }
444
445    /// Returns the JSON Pointer to the instance location that failed validation.
446    #[inline]
447    #[must_use]
448    pub fn instance_path(&self) -> &Location {
449        &self.repr.instance_path
450    }
451
452    /// Returns the canonical schema location without `$ref` traversals.
453    ///
454    /// This corresponds to JSON Schema's "keywordLocation" in output formats.
455    /// See JSON Schema 2020-12 Core, Section 12.4.2.
456    #[inline]
457    #[must_use]
458    pub fn schema_path(&self) -> &Location {
459        &self.repr.schema_path
460    }
461
462    /// Returns the dynamic evaluation path including `$ref` traversals.
463    ///
464    /// This corresponds to JSON Schema's "evaluationPath" - the actual path taken
465    /// through the schema including by-reference applicators (`$ref`, `$dynamicRef`).
466    /// See JSON Schema 2020-12 Core, Section 12.4.2.
467    #[inline]
468    #[must_use]
469    pub fn evaluation_path(&self) -> &Location {
470        self.repr.tracker.resolve(&self.repr.schema_path)
471    }
472
473    /// Returns the absolute keyword location as a full URI including JSON Pointer fragment.
474    ///
475    /// This corresponds to JSON Schema's "absoluteKeywordLocation" (e.g.
476    /// `https://example.com/schema.json#/properties/name/type`).
477    ///
478    /// Present when the schema has a real base URI set via `$id` or [`crate::options::ValidationOptions::with_base_uri`].
479    /// `None` for schemas without an explicit base URI.
480    #[inline]
481    #[must_use]
482    pub fn absolute_keyword_location(&self) -> Option<&Uri<String>> {
483        self.repr.absolute_keyword_location.as_deref()
484    }
485
486    /// Attach an absolute keyword location if one is not already set.
487    ///
488    /// Uses "set if not already set" semantics so inner `SchemaNode` calls win over outer ones.
489    #[inline]
490    pub(crate) fn with_absolute_keyword_location(mut self, uri: Option<Arc<Uri<String>>>) -> Self {
491        if self.repr.absolute_keyword_location.is_none() {
492            self.repr.absolute_keyword_location = uri;
493        }
494        self
495    }
496
497    /// Decomposes the error into its owned parts.
498    #[inline]
499    #[must_use]
500    pub fn into_parts(self) -> ValidationErrorParts<'a> {
501        let repr = *self.repr;
502        let evaluation_path = repr.tracker.into_owned(repr.schema_path.clone());
503        ValidationErrorParts {
504            instance: repr.instance,
505            kind: repr.kind,
506            instance_path: repr.instance_path,
507            schema_path: repr.schema_path,
508            evaluation_path,
509            absolute_keyword_location: repr.absolute_keyword_location,
510        }
511    }
512
513    #[inline]
514    fn borrowed(
515        instance: &'a Value,
516        kind: ValidationErrorKind,
517        instance_path: Location,
518        schema_path: Location,
519        tracker: impl Into<LazyEvaluationPath>,
520    ) -> Self {
521        Self::new(
522            Cow::Borrowed(instance),
523            kind,
524            instance_path,
525            schema_path,
526            tracker,
527        )
528    }
529
530    /// Returns a wrapper that masks instance values in error messages.
531    /// Uses "value" as a default placeholder.
532    #[must_use]
533    pub fn masked<'b>(&'b self) -> MaskedValidationError<'a, 'b, 'static> {
534        self.masked_with("value")
535    }
536
537    /// Returns a wrapper that masks instance values in error messages with a custom placeholder.
538    pub fn masked_with<'b, 'c>(
539        &'b self,
540        placeholder: impl Into<Cow<'c, str>>,
541    ) -> MaskedValidationError<'a, 'b, 'c> {
542        MaskedValidationError {
543            error: self,
544            placeholder: placeholder.into(),
545        }
546    }
547    /// Converts the `ValidationError` into an owned version with `'static` lifetime.
548    #[must_use]
549    pub fn to_owned(self) -> ValidationError<'static> {
550        let parts = self.into_parts();
551        ValidationError::new(
552            Cow::Owned(parts.instance.into_owned()),
553            parts.kind,
554            parts.instance_path,
555            parts.schema_path,
556            parts.evaluation_path,
557        )
558        .with_absolute_keyword_location(parts.absolute_keyword_location)
559    }
560
561    pub(crate) fn additional_items(
562        schema_path: Location,
563        tracker: impl Into<LazyEvaluationPath>,
564        instance_path: Location,
565        instance: &'a Value,
566        limit: usize,
567    ) -> ValidationError<'a> {
568        Self::borrowed(
569            instance,
570            ValidationErrorKind::AdditionalItems { limit },
571            instance_path,
572            schema_path,
573            tracker,
574        )
575    }
576    pub(crate) fn additional_properties(
577        schema_path: Location,
578        tracker: impl Into<LazyEvaluationPath>,
579        instance_path: Location,
580        instance: &'a Value,
581        unexpected: Vec<String>,
582    ) -> ValidationError<'a> {
583        Self::borrowed(
584            instance,
585            ValidationErrorKind::AdditionalProperties { unexpected },
586            instance_path,
587            schema_path,
588            tracker,
589        )
590    }
591    pub(crate) fn any_of(
592        schema_path: Location,
593        tracker: impl Into<LazyEvaluationPath>,
594        instance_path: Location,
595        instance: &'a Value,
596        context: Vec<Vec<ValidationError<'a>>>,
597    ) -> ValidationError<'a> {
598        let context = context
599            .into_iter()
600            .map(|errors| errors.into_iter().map(ValidationError::to_owned).collect())
601            .collect::<Vec<_>>();
602
603        Self::borrowed(
604            instance,
605            ValidationErrorKind::AnyOf { context },
606            instance_path,
607            schema_path,
608            tracker,
609        )
610    }
611    pub(crate) fn backtrack_limit(
612        schema_path: Location,
613        tracker: impl Into<LazyEvaluationPath>,
614        instance_path: Location,
615        instance: &'a Value,
616        error: fancy_regex::Error,
617    ) -> ValidationError<'a> {
618        Self::borrowed(
619            instance,
620            ValidationErrorKind::BacktrackLimitExceeded { error },
621            instance_path,
622            schema_path,
623            tracker,
624        )
625    }
626    pub(crate) fn constant_array(
627        schema_path: Location,
628        tracker: impl Into<LazyEvaluationPath>,
629        instance_path: Location,
630        instance: &'a Value,
631        expected_value: &[Value],
632    ) -> ValidationError<'a> {
633        Self::borrowed(
634            instance,
635            ValidationErrorKind::Constant {
636                expected_value: Value::Array(expected_value.to_vec()),
637            },
638            instance_path,
639            schema_path,
640            tracker,
641        )
642    }
643    pub(crate) fn constant_boolean(
644        schema_path: Location,
645        tracker: impl Into<LazyEvaluationPath>,
646        instance_path: Location,
647        instance: &'a Value,
648        expected_value: bool,
649    ) -> ValidationError<'a> {
650        Self::borrowed(
651            instance,
652            ValidationErrorKind::Constant {
653                expected_value: Value::Bool(expected_value),
654            },
655            instance_path,
656            schema_path,
657            tracker,
658        )
659    }
660    pub(crate) fn constant_null(
661        schema_path: Location,
662        tracker: impl Into<LazyEvaluationPath>,
663        instance_path: Location,
664        instance: &'a Value,
665    ) -> ValidationError<'a> {
666        Self::borrowed(
667            instance,
668            ValidationErrorKind::Constant {
669                expected_value: Value::Null,
670            },
671            instance_path,
672            schema_path,
673            tracker,
674        )
675    }
676    pub(crate) fn constant_number(
677        schema_path: Location,
678        tracker: impl Into<LazyEvaluationPath>,
679        instance_path: Location,
680        instance: &'a Value,
681        expected_value: &Number,
682    ) -> ValidationError<'a> {
683        Self::borrowed(
684            instance,
685            ValidationErrorKind::Constant {
686                expected_value: Value::Number(expected_value.clone()),
687            },
688            instance_path,
689            schema_path,
690            tracker,
691        )
692    }
693    pub(crate) fn constant_object(
694        schema_path: Location,
695        tracker: impl Into<LazyEvaluationPath>,
696        instance_path: Location,
697        instance: &'a Value,
698        expected_value: &Map<String, Value>,
699    ) -> ValidationError<'a> {
700        Self::borrowed(
701            instance,
702            ValidationErrorKind::Constant {
703                expected_value: Value::Object(expected_value.clone()),
704            },
705            instance_path,
706            schema_path,
707            tracker,
708        )
709    }
710    pub(crate) fn constant_string(
711        schema_path: Location,
712        tracker: impl Into<LazyEvaluationPath>,
713        instance_path: Location,
714        instance: &'a Value,
715        expected_value: &str,
716    ) -> ValidationError<'a> {
717        Self::borrowed(
718            instance,
719            ValidationErrorKind::Constant {
720                expected_value: Value::String(expected_value.to_string()),
721            },
722            instance_path,
723            schema_path,
724            tracker,
725        )
726    }
727    pub(crate) fn contains(
728        schema_path: Location,
729        tracker: impl Into<LazyEvaluationPath>,
730        instance_path: Location,
731        instance: &'a Value,
732    ) -> ValidationError<'a> {
733        Self::borrowed(
734            instance,
735            ValidationErrorKind::Contains,
736            instance_path,
737            schema_path,
738            tracker,
739        )
740    }
741    pub(crate) fn content_encoding(
742        schema_path: Location,
743        tracker: impl Into<LazyEvaluationPath>,
744        instance_path: Location,
745        instance: &'a Value,
746        encoding: &str,
747    ) -> ValidationError<'a> {
748        Self::borrowed(
749            instance,
750            ValidationErrorKind::ContentEncoding {
751                content_encoding: encoding.to_string(),
752            },
753            instance_path,
754            schema_path,
755            tracker,
756        )
757    }
758    pub(crate) fn content_media_type(
759        schema_path: Location,
760        tracker: impl Into<LazyEvaluationPath>,
761        instance_path: Location,
762        instance: &'a Value,
763        media_type: &str,
764    ) -> ValidationError<'a> {
765        Self::borrowed(
766            instance,
767            ValidationErrorKind::ContentMediaType {
768                content_media_type: media_type.to_string(),
769            },
770            instance_path,
771            schema_path,
772            tracker,
773        )
774    }
775    pub(crate) fn enumeration(
776        schema_path: Location,
777        tracker: impl Into<LazyEvaluationPath>,
778        instance_path: Location,
779        instance: &'a Value,
780        options: &Value,
781    ) -> ValidationError<'a> {
782        Self::borrowed(
783            instance,
784            ValidationErrorKind::Enum {
785                options: options.clone(),
786            },
787            instance_path,
788            schema_path,
789            tracker,
790        )
791    }
792    pub(crate) fn exclusive_maximum(
793        schema_path: Location,
794        tracker: impl Into<LazyEvaluationPath>,
795        instance_path: Location,
796        instance: &'a Value,
797        limit: Value,
798    ) -> ValidationError<'a> {
799        Self::borrowed(
800            instance,
801            ValidationErrorKind::ExclusiveMaximum { limit },
802            instance_path,
803            schema_path,
804            tracker,
805        )
806    }
807    pub(crate) fn exclusive_minimum(
808        schema_path: Location,
809        tracker: impl Into<LazyEvaluationPath>,
810        instance_path: Location,
811        instance: &'a Value,
812        limit: Value,
813    ) -> ValidationError<'a> {
814        Self::borrowed(
815            instance,
816            ValidationErrorKind::ExclusiveMinimum { limit },
817            instance_path,
818            schema_path,
819            tracker,
820        )
821    }
822    pub(crate) fn false_schema(
823        schema_path: Location,
824        tracker: impl Into<LazyEvaluationPath>,
825        instance_path: Location,
826        instance: &'a Value,
827    ) -> ValidationError<'a> {
828        Self::borrowed(
829            instance,
830            ValidationErrorKind::FalseSchema,
831            instance_path,
832            schema_path,
833            tracker,
834        )
835    }
836    pub(crate) fn format(
837        schema_path: Location,
838        tracker: impl Into<LazyEvaluationPath>,
839        instance_path: Location,
840        instance: &'a Value,
841        format: impl Into<String>,
842    ) -> ValidationError<'a> {
843        Self::borrowed(
844            instance,
845            ValidationErrorKind::Format {
846                format: format.into(),
847            },
848            instance_path,
849            schema_path,
850            tracker,
851        )
852    }
853    pub(crate) fn from_utf8(error: FromUtf8Error) -> ValidationError<'a> {
854        ValidationError::new(
855            Cow::Owned(Value::Null),
856            ValidationErrorKind::FromUtf8 { error },
857            Location::new(),
858            Location::new(),
859            Location::new(),
860        )
861    }
862    pub(crate) fn max_items(
863        schema_path: Location,
864        tracker: impl Into<LazyEvaluationPath>,
865        instance_path: Location,
866        instance: &'a Value,
867        limit: u64,
868    ) -> ValidationError<'a> {
869        Self::borrowed(
870            instance,
871            ValidationErrorKind::MaxItems { limit },
872            instance_path,
873            schema_path,
874            tracker,
875        )
876    }
877    pub(crate) fn maximum(
878        schema_path: Location,
879        tracker: impl Into<LazyEvaluationPath>,
880        instance_path: Location,
881        instance: &'a Value,
882        limit: Value,
883    ) -> ValidationError<'a> {
884        Self::borrowed(
885            instance,
886            ValidationErrorKind::Maximum { limit },
887            instance_path,
888            schema_path,
889            tracker,
890        )
891    }
892    pub(crate) fn max_length(
893        schema_path: Location,
894        tracker: impl Into<LazyEvaluationPath>,
895        instance_path: Location,
896        instance: &'a Value,
897        limit: u64,
898    ) -> ValidationError<'a> {
899        Self::borrowed(
900            instance,
901            ValidationErrorKind::MaxLength { limit },
902            instance_path,
903            schema_path,
904            tracker,
905        )
906    }
907    pub(crate) fn max_properties(
908        schema_path: Location,
909        tracker: impl Into<LazyEvaluationPath>,
910        instance_path: Location,
911        instance: &'a Value,
912        limit: u64,
913    ) -> ValidationError<'a> {
914        Self::borrowed(
915            instance,
916            ValidationErrorKind::MaxProperties { limit },
917            instance_path,
918            schema_path,
919            tracker,
920        )
921    }
922    pub(crate) fn min_items(
923        schema_path: Location,
924        tracker: impl Into<LazyEvaluationPath>,
925        instance_path: Location,
926        instance: &'a Value,
927        limit: u64,
928    ) -> ValidationError<'a> {
929        Self::borrowed(
930            instance,
931            ValidationErrorKind::MinItems { limit },
932            instance_path,
933            schema_path,
934            tracker,
935        )
936    }
937    pub(crate) fn minimum(
938        schema_path: Location,
939        tracker: impl Into<LazyEvaluationPath>,
940        instance_path: Location,
941        instance: &'a Value,
942        limit: Value,
943    ) -> ValidationError<'a> {
944        Self::borrowed(
945            instance,
946            ValidationErrorKind::Minimum { limit },
947            instance_path,
948            schema_path,
949            tracker,
950        )
951    }
952    pub(crate) fn min_length(
953        schema_path: Location,
954        tracker: impl Into<LazyEvaluationPath>,
955        instance_path: Location,
956        instance: &'a Value,
957        limit: u64,
958    ) -> ValidationError<'a> {
959        Self::borrowed(
960            instance,
961            ValidationErrorKind::MinLength { limit },
962            instance_path,
963            schema_path,
964            tracker,
965        )
966    }
967    pub(crate) fn min_properties(
968        schema_path: Location,
969        tracker: impl Into<LazyEvaluationPath>,
970        instance_path: Location,
971        instance: &'a Value,
972        limit: u64,
973    ) -> ValidationError<'a> {
974        Self::borrowed(
975            instance,
976            ValidationErrorKind::MinProperties { limit },
977            instance_path,
978            schema_path,
979            tracker,
980        )
981    }
982    #[cfg(feature = "arbitrary-precision")]
983    pub(crate) fn multiple_of(
984        schema_path: Location,
985        tracker: impl Into<LazyEvaluationPath>,
986        instance_path: Location,
987        instance: &'a Value,
988        multiple_of: Value,
989    ) -> ValidationError<'a> {
990        Self::borrowed(
991            instance,
992            ValidationErrorKind::MultipleOf { multiple_of },
993            instance_path,
994            schema_path,
995            tracker,
996        )
997    }
998
999    #[cfg(not(feature = "arbitrary-precision"))]
1000    pub(crate) fn multiple_of(
1001        schema_path: Location,
1002        tracker: impl Into<LazyEvaluationPath>,
1003        instance_path: Location,
1004        instance: &'a Value,
1005        multiple_of: f64,
1006    ) -> ValidationError<'a> {
1007        Self::borrowed(
1008            instance,
1009            ValidationErrorKind::MultipleOf { multiple_of },
1010            instance_path,
1011            schema_path,
1012            tracker,
1013        )
1014    }
1015    pub(crate) fn not(
1016        schema_path: Location,
1017        tracker: impl Into<LazyEvaluationPath>,
1018        instance_path: Location,
1019        instance: &'a Value,
1020        schema: Value,
1021    ) -> ValidationError<'a> {
1022        Self::borrowed(
1023            instance,
1024            ValidationErrorKind::Not { schema },
1025            instance_path,
1026            schema_path,
1027            tracker,
1028        )
1029    }
1030    pub(crate) fn one_of_multiple_valid(
1031        schema_path: Location,
1032        tracker: impl Into<LazyEvaluationPath>,
1033        instance_path: Location,
1034        instance: &'a Value,
1035        context: Vec<Vec<ValidationError<'a>>>,
1036    ) -> ValidationError<'a> {
1037        let context = context
1038            .into_iter()
1039            .map(|errors| errors.into_iter().map(ValidationError::to_owned).collect())
1040            .collect::<Vec<_>>();
1041
1042        Self::borrowed(
1043            instance,
1044            ValidationErrorKind::OneOfMultipleValid { context },
1045            instance_path,
1046            schema_path,
1047            tracker,
1048        )
1049    }
1050    pub(crate) fn one_of_not_valid(
1051        schema_path: Location,
1052        tracker: impl Into<LazyEvaluationPath>,
1053        instance_path: Location,
1054        instance: &'a Value,
1055        context: Vec<Vec<ValidationError<'a>>>,
1056    ) -> ValidationError<'a> {
1057        let context = context
1058            .into_iter()
1059            .map(|errors| errors.into_iter().map(ValidationError::to_owned).collect())
1060            .collect::<Vec<_>>();
1061
1062        Self::borrowed(
1063            instance,
1064            ValidationErrorKind::OneOfNotValid { context },
1065            instance_path,
1066            schema_path,
1067            tracker,
1068        )
1069    }
1070    pub(crate) fn pattern(
1071        schema_path: Location,
1072        tracker: impl Into<LazyEvaluationPath>,
1073        instance_path: Location,
1074        instance: &'a Value,
1075        pattern: String,
1076    ) -> ValidationError<'a> {
1077        Self::borrowed(
1078            instance,
1079            ValidationErrorKind::Pattern { pattern },
1080            instance_path,
1081            schema_path,
1082            tracker,
1083        )
1084    }
1085    pub(crate) fn property_names(
1086        schema_path: Location,
1087        tracker: impl Into<LazyEvaluationPath>,
1088        instance_path: Location,
1089        instance: &'a Value,
1090        error: ValidationError<'a>,
1091    ) -> ValidationError<'a> {
1092        Self::borrowed(
1093            instance,
1094            ValidationErrorKind::PropertyNames {
1095                error: Box::new(error.to_owned()),
1096            },
1097            instance_path,
1098            schema_path,
1099            tracker,
1100        )
1101    }
1102    pub(crate) fn required(
1103        schema_path: Location,
1104        tracker: impl Into<LazyEvaluationPath>,
1105        instance_path: Location,
1106        instance: &'a Value,
1107        property: Value,
1108    ) -> ValidationError<'a> {
1109        Self::borrowed(
1110            instance,
1111            ValidationErrorKind::Required { property },
1112            instance_path,
1113            schema_path,
1114            tracker,
1115        )
1116    }
1117
1118    pub(crate) fn single_type_error(
1119        schema_path: Location,
1120        tracker: impl Into<LazyEvaluationPath>,
1121        instance_path: Location,
1122        instance: &'a Value,
1123        type_name: JsonType,
1124    ) -> ValidationError<'a> {
1125        Self::borrowed(
1126            instance,
1127            ValidationErrorKind::Type {
1128                kind: TypeKind::Single(type_name),
1129            },
1130            instance_path,
1131            schema_path,
1132            tracker,
1133        )
1134    }
1135    pub(crate) fn multiple_type_error(
1136        schema_path: Location,
1137        tracker: impl Into<LazyEvaluationPath>,
1138        instance_path: Location,
1139        instance: &'a Value,
1140        types: JsonTypeSet,
1141    ) -> ValidationError<'a> {
1142        Self::borrowed(
1143            instance,
1144            ValidationErrorKind::Type {
1145                kind: TypeKind::Multiple(types),
1146            },
1147            instance_path,
1148            schema_path,
1149            tracker,
1150        )
1151    }
1152    pub(crate) fn unevaluated_items(
1153        schema_path: Location,
1154        tracker: impl Into<LazyEvaluationPath>,
1155        instance_path: Location,
1156        instance: &'a Value,
1157        unexpected: Vec<String>,
1158    ) -> ValidationError<'a> {
1159        Self::borrowed(
1160            instance,
1161            ValidationErrorKind::UnevaluatedItems { unexpected },
1162            instance_path,
1163            schema_path,
1164            tracker,
1165        )
1166    }
1167    pub(crate) fn unevaluated_properties(
1168        schema_path: Location,
1169        tracker: impl Into<LazyEvaluationPath>,
1170        instance_path: Location,
1171        instance: &'a Value,
1172        unexpected: Vec<String>,
1173    ) -> ValidationError<'a> {
1174        Self::borrowed(
1175            instance,
1176            ValidationErrorKind::UnevaluatedProperties { unexpected },
1177            instance_path,
1178            schema_path,
1179            tracker,
1180        )
1181    }
1182    pub(crate) fn unique_items(
1183        schema_path: Location,
1184        tracker: impl Into<LazyEvaluationPath>,
1185        instance_path: Location,
1186        instance: &'a Value,
1187    ) -> ValidationError<'a> {
1188        Self::borrowed(
1189            instance,
1190            ValidationErrorKind::UniqueItems,
1191            instance_path,
1192            schema_path,
1193            tracker,
1194        )
1195    }
1196    /// Create a custom validation error with just a message.
1197    ///
1198    /// Use this in [`Keyword::validate`](crate::Keyword::validate) implementations.
1199    /// The actual instance, instance path, and schema path are filled in automatically.
1200    ///
1201    /// # Example
1202    ///
1203    /// ```rust
1204    /// use jsonschema::ValidationError;
1205    ///
1206    /// fn validate_even(n: u64) -> Result<(), ValidationError<'static>> {
1207    ///     if n % 2 != 0 {
1208    ///         return Err(ValidationError::custom("number must be even"));
1209    ///     }
1210    ///     Ok(())
1211    /// }
1212    /// ```
1213    pub fn custom(message: impl Into<String>) -> ValidationError<'static> {
1214        ValidationError::new(
1215            Cow::Owned(Value::Null),
1216            ValidationErrorKind::Custom {
1217                keyword: String::new(),
1218                message: message.into(),
1219            },
1220            Location::new(),
1221            Location::new(),
1222            Location::new(),
1223        )
1224    }
1225
1226    /// Create an error for invalid schema values in keyword factories.
1227    ///
1228    /// Use this in custom keyword factory functions when the schema value
1229    /// is invalid for your custom keyword.
1230    pub fn schema(message: impl Into<String>) -> ValidationError<'static> {
1231        ValidationError::new(
1232            Cow::Owned(Value::Null),
1233            ValidationErrorKind::Custom {
1234                keyword: String::new(),
1235                message: message.into(),
1236            },
1237            Location::new(),
1238            Location::new(),
1239            Location::new(),
1240        )
1241    }
1242
1243    /// Fill in context for a placeholder validation error.
1244    ///
1245    /// Used by custom keywords, which never involve `$ref` traversal,
1246    /// so evaluation path equals schema path.
1247    pub(crate) fn with_context<'i>(
1248        self,
1249        instance: &'i Value,
1250        instance_path: &LazyLocation,
1251        schema_path: &Location,
1252        keyword: &str,
1253    ) -> ValidationError<'i> {
1254        let kind = match self.repr.kind {
1255            ValidationErrorKind::Custom { message, .. } => ValidationErrorKind::Custom {
1256                keyword: keyword.to_string(),
1257                message,
1258            },
1259            other => other,
1260        };
1261        ValidationError::new(
1262            Cow::Borrowed(instance),
1263            kind,
1264            instance_path.into(),
1265            schema_path.clone(),
1266            // Custom keywords are never reached via $ref, so evaluation path = schema path
1267            LazyEvaluationPath::SameAsSchemaPath,
1268        )
1269    }
1270
1271    /// Fill in context for a placeholder schema error (used in keyword factories).
1272    pub(crate) fn with_schema_context<'s>(
1273        self,
1274        schema_value: &'s Value,
1275        schema_path: Location,
1276        keyword: &str,
1277    ) -> ValidationError<'s> {
1278        let kind = match self.repr.kind {
1279            ValidationErrorKind::Custom { message, .. } => ValidationErrorKind::Custom {
1280                keyword: keyword.into(),
1281                message,
1282            },
1283            other => other,
1284        };
1285        ValidationError::new(
1286            Cow::Borrowed(schema_value),
1287            kind,
1288            Location::new(),
1289            schema_path.clone(),
1290            schema_path,
1291        )
1292    }
1293
1294    pub(crate) fn compile_error(
1295        schema_path: Location,
1296        tracker: impl Into<LazyEvaluationPath>,
1297        instance_path: Location,
1298        instance: &'a Value,
1299        message: impl Into<String>,
1300    ) -> ValidationError<'a> {
1301        Self::borrowed(
1302            instance,
1303            ValidationErrorKind::Custom {
1304                keyword: String::new(),
1305                message: message.into(),
1306            },
1307            instance_path,
1308            schema_path,
1309            tracker,
1310        )
1311    }
1312}
1313
1314impl error::Error for ValidationError<'_> {}
1315impl From<referencing::Error> for ValidationError<'_> {
1316    #[inline]
1317    fn from(err: referencing::Error) -> Self {
1318        ValidationError::new(
1319            Cow::Owned(Value::Null),
1320            ValidationErrorKind::Referencing(err),
1321            Location::new(),
1322            Location::new(),
1323            Location::new(),
1324        )
1325    }
1326}
1327impl From<FromUtf8Error> for ValidationError<'_> {
1328    #[inline]
1329    fn from(err: FromUtf8Error) -> Self {
1330        ValidationError::from_utf8(err)
1331    }
1332}
1333
1334fn write_quoted_list(f: &mut Formatter<'_>, items: &[impl fmt::Display]) -> fmt::Result {
1335    let mut iter = items.iter();
1336    if let Some(item) = iter.next() {
1337        f.write_char('\'')?;
1338        write!(f, "{item}")?;
1339        f.write_char('\'')?;
1340    }
1341    for item in iter {
1342        f.write_str(", ")?;
1343        f.write_char('\'')?;
1344        write!(f, "{item}")?;
1345        f.write_char('\'')?;
1346    }
1347    Ok(())
1348}
1349
1350fn write_unexpected_suffix(f: &mut Formatter<'_>, len: usize) -> fmt::Result {
1351    f.write_str(if len == 1 {
1352        " was unexpected)"
1353    } else {
1354        " were unexpected)"
1355    })
1356}
1357
1358const MAX_DISPLAYED_ENUM_VARIANTS: usize = 3;
1359
1360fn write_enum_message(
1361    f: &mut Formatter<'_>,
1362    value: impl fmt::Display,
1363    options: &Value,
1364) -> fmt::Result {
1365    let array = options
1366        .as_array()
1367        .expect("Enum options must be a JSON array");
1368
1369    write!(f, "{value} is not one of ")?;
1370
1371    let total_count = array.len();
1372
1373    if total_count <= MAX_DISPLAYED_ENUM_VARIANTS {
1374        // Show all options with proper "a, b or c" formatting
1375        for (i, option) in array.iter().enumerate() {
1376            if i == 0 {
1377                write!(f, "{option}")?;
1378            } else if i == total_count - 1 {
1379                write!(f, " or {option}")?;
1380            } else {
1381                write!(f, ", {option}")?;
1382            }
1383        }
1384    } else {
1385        // Show first few, then "or X other candidates"
1386        let show_count = MAX_DISPLAYED_ENUM_VARIANTS - 1;
1387        for (i, option) in array.iter().take(show_count).enumerate() {
1388            if i == 0 {
1389                write!(f, "{option}")?;
1390            } else {
1391                write!(f, ", {option}")?;
1392            }
1393        }
1394        let remaining = total_count - show_count;
1395        write!(f, " or {remaining} other candidates")?;
1396    }
1397    Ok(())
1398}
1399
1400/// Textual representation of various validation errors.
1401impl fmt::Display for ValidationError<'_> {
1402    #[allow(clippy::too_many_lines)] // The function is long but it does formatting only
1403    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1404        match self.kind() {
1405            ValidationErrorKind::Referencing(error) => error.fmt(f),
1406            ValidationErrorKind::BacktrackLimitExceeded { error } => error.fmt(f),
1407            ValidationErrorKind::Format { format } => {
1408                write!(f, r#"{} is not a "{}""#, self.instance(), format)
1409            }
1410            ValidationErrorKind::AdditionalItems { limit } => {
1411                f.write_str("Additional items are not allowed (")?;
1412                let array = self.instance().as_array().expect("Always valid");
1413                let mut iter = array.iter().skip(*limit);
1414
1415                if let Some(item) = iter.next() {
1416                    write!(f, "{item}")?;
1417                }
1418                for item in iter {
1419                    f.write_str(", ")?;
1420                    write!(f, "{item}")?;
1421                }
1422
1423                write_unexpected_suffix(f, array.len() - limit)
1424            }
1425            ValidationErrorKind::AdditionalProperties { unexpected } => {
1426                f.write_str("Additional properties are not allowed (")?;
1427                write_quoted_list(f, unexpected)?;
1428                write_unexpected_suffix(f, unexpected.len())
1429            }
1430            ValidationErrorKind::AnyOf { context: _ } => write!(
1431                f,
1432                "{} is not valid under any of the schemas listed in the 'anyOf' keyword",
1433                self.instance()
1434            ),
1435            ValidationErrorKind::OneOfNotValid { context: _ } => write!(
1436                f,
1437                "{} is not valid under any of the schemas listed in the 'oneOf' keyword",
1438                self.instance()
1439            ),
1440            ValidationErrorKind::Contains => write!(
1441                f,
1442                "None of {} are valid under the given schema",
1443                self.instance()
1444            ),
1445            ValidationErrorKind::Constant { expected_value } => {
1446                write!(f, "{expected_value} was expected")
1447            }
1448            ValidationErrorKind::ContentEncoding { content_encoding } => {
1449                write!(
1450                    f,
1451                    r#"{} is not compliant with "{}" content encoding"#,
1452                    self.instance(),
1453                    content_encoding
1454                )
1455            }
1456            ValidationErrorKind::ContentMediaType { content_media_type } => {
1457                write!(
1458                    f,
1459                    r#"{} is not compliant with "{}" media type"#,
1460                    self.instance(),
1461                    content_media_type
1462                )
1463            }
1464            ValidationErrorKind::FromUtf8 { error } => error.fmt(f),
1465            ValidationErrorKind::Enum { options } => {
1466                write_enum_message(f, self.instance(), options)
1467            }
1468            ValidationErrorKind::ExclusiveMaximum { limit } => write!(
1469                f,
1470                "{} is greater than or equal to the maximum of {}",
1471                self.instance(),
1472                limit
1473            ),
1474            ValidationErrorKind::ExclusiveMinimum { limit } => write!(
1475                f,
1476                "{} is less than or equal to the minimum of {}",
1477                self.instance(),
1478                limit
1479            ),
1480            ValidationErrorKind::FalseSchema => {
1481                write!(f, "False schema does not allow {}", self.instance())
1482            }
1483            ValidationErrorKind::Maximum { limit } => write!(
1484                f,
1485                "{} is greater than the maximum of {}",
1486                self.instance(),
1487                limit
1488            ),
1489            ValidationErrorKind::Minimum { limit } => {
1490                write!(
1491                    f,
1492                    "{} is less than the minimum of {}",
1493                    self.instance(),
1494                    limit
1495                )
1496            }
1497            ValidationErrorKind::MaxLength { limit } => write!(
1498                f,
1499                "{} is longer than {} character{}",
1500                self.instance(),
1501                limit,
1502                if *limit == 1 { "" } else { "s" }
1503            ),
1504            ValidationErrorKind::MinLength { limit } => write!(
1505                f,
1506                "{} is shorter than {} character{}",
1507                self.instance(),
1508                limit,
1509                if *limit == 1 { "" } else { "s" }
1510            ),
1511            ValidationErrorKind::MaxItems { limit } => write!(
1512                f,
1513                "{} has more than {} item{}",
1514                self.instance(),
1515                limit,
1516                if *limit == 1 { "" } else { "s" }
1517            ),
1518            ValidationErrorKind::MinItems { limit } => write!(
1519                f,
1520                "{} has less than {} item{}",
1521                self.instance(),
1522                limit,
1523                if *limit == 1 { "" } else { "s" }
1524            ),
1525            ValidationErrorKind::MaxProperties { limit } => write!(
1526                f,
1527                "{} has more than {} propert{}",
1528                self.instance(),
1529                limit,
1530                if *limit == 1 { "y" } else { "ies" }
1531            ),
1532            ValidationErrorKind::MinProperties { limit } => write!(
1533                f,
1534                "{} has less than {} propert{}",
1535                self.instance(),
1536                limit,
1537                if *limit == 1 { "y" } else { "ies" }
1538            ),
1539            ValidationErrorKind::Not { schema } => {
1540                write!(f, "{} is not allowed for {}", schema, self.instance())
1541            }
1542            ValidationErrorKind::OneOfMultipleValid { .. } => write!(
1543                f,
1544                "{} is valid under more than one of the schemas listed in the 'oneOf' keyword",
1545                self.instance()
1546            ),
1547            ValidationErrorKind::Pattern { pattern } => {
1548                write!(f, r#"{} does not match "{}""#, self.instance(), pattern)
1549            }
1550            ValidationErrorKind::PropertyNames { error } => error.fmt(f),
1551            ValidationErrorKind::Required { property } => {
1552                write!(f, "{property} is a required property")
1553            }
1554            ValidationErrorKind::MultipleOf { multiple_of } => {
1555                write!(
1556                    f,
1557                    "{} is not a multiple of {}",
1558                    self.instance(),
1559                    multiple_of
1560                )
1561            }
1562            ValidationErrorKind::UnevaluatedItems { unexpected } => {
1563                f.write_str("Unevaluated items are not allowed (")?;
1564                write_quoted_list(f, unexpected)?;
1565                write_unexpected_suffix(f, unexpected.len())
1566            }
1567            ValidationErrorKind::UnevaluatedProperties { unexpected } => {
1568                f.write_str("Unevaluated properties are not allowed (")?;
1569                write_quoted_list(f, unexpected)?;
1570                write_unexpected_suffix(f, unexpected.len())
1571            }
1572            ValidationErrorKind::UniqueItems => {
1573                write!(f, "{} has non-unique elements", self.instance())
1574            }
1575            ValidationErrorKind::Type {
1576                kind: TypeKind::Single(type_),
1577            } => write!(f, r#"{} is not of type "{}""#, self.instance(), type_),
1578            ValidationErrorKind::Type {
1579                kind: TypeKind::Multiple(types),
1580            } => {
1581                write!(f, "{} is not of types ", self.instance())?;
1582                let mut iter = types.iter();
1583                if let Some(t) = iter.next() {
1584                    f.write_char('"')?;
1585                    write!(f, "{t}")?;
1586                    f.write_char('"')?;
1587                }
1588                for t in iter {
1589                    f.write_str(", ")?;
1590                    f.write_char('"')?;
1591                    write!(f, "{t}")?;
1592                    f.write_char('"')?;
1593                }
1594                Ok(())
1595            }
1596            ValidationErrorKind::Custom { message, .. } => f.write_str(message),
1597        }
1598    }
1599}
1600
1601/// A wrapper that provides a masked display of validation errors.
1602pub struct MaskedValidationError<'a, 'b, 'c> {
1603    error: &'b ValidationError<'a>,
1604    placeholder: Cow<'c, str>,
1605}
1606
1607impl fmt::Display for MaskedValidationError<'_, '_, '_> {
1608    #[allow(clippy::too_many_lines)]
1609    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1610        match self.error.kind() {
1611            ValidationErrorKind::Referencing(error) => error.fmt(f),
1612            ValidationErrorKind::BacktrackLimitExceeded { error } => error.fmt(f),
1613            ValidationErrorKind::Format { format } => {
1614                write!(f, r#"{} is not a "{format}""#, self.placeholder)
1615            }
1616            ValidationErrorKind::AdditionalItems { limit } => {
1617                write!(f, "Additional items are not allowed ({limit} items)")
1618            }
1619            ValidationErrorKind::AdditionalProperties { unexpected } => {
1620                f.write_str("Additional properties are not allowed (")?;
1621                write_quoted_list(f, unexpected)?;
1622                write_unexpected_suffix(f, unexpected.len())
1623            }
1624            ValidationErrorKind::AnyOf { .. } => write!(
1625                f,
1626                "{} is not valid under any of the schemas listed in the 'anyOf' keyword",
1627                self.placeholder
1628            ),
1629            ValidationErrorKind::OneOfNotValid { context: _ } => write!(
1630                f,
1631                "{} is not valid under any of the schemas listed in the 'oneOf' keyword",
1632                self.placeholder
1633            ),
1634            ValidationErrorKind::Contains => write!(
1635                f,
1636                "None of {} are valid under the given schema",
1637                self.placeholder
1638            ),
1639            ValidationErrorKind::Constant { expected_value } => {
1640                write!(f, "{expected_value} was expected")
1641            }
1642            ValidationErrorKind::ContentEncoding { content_encoding } => {
1643                write!(
1644                    f,
1645                    r#"{} is not compliant with "{}" content encoding"#,
1646                    self.placeholder, content_encoding
1647                )
1648            }
1649            ValidationErrorKind::ContentMediaType { content_media_type } => {
1650                write!(
1651                    f,
1652                    r#"{} is not compliant with "{}" media type"#,
1653                    self.placeholder, content_media_type
1654                )
1655            }
1656            ValidationErrorKind::FromUtf8 { error } => error.fmt(f),
1657            ValidationErrorKind::Enum { options } => {
1658                write_enum_message(f, &self.placeholder, options)
1659            }
1660            ValidationErrorKind::ExclusiveMaximum { limit } => write!(
1661                f,
1662                "{} is greater than or equal to the maximum of {}",
1663                self.placeholder, limit
1664            ),
1665            ValidationErrorKind::ExclusiveMinimum { limit } => write!(
1666                f,
1667                "{} is less than or equal to the minimum of {}",
1668                self.placeholder, limit
1669            ),
1670            ValidationErrorKind::FalseSchema => {
1671                write!(f, "False schema does not allow {}", self.placeholder)
1672            }
1673            ValidationErrorKind::Maximum { limit } => write!(
1674                f,
1675                "{} is greater than the maximum of {}",
1676                self.placeholder, limit
1677            ),
1678            ValidationErrorKind::Minimum { limit } => {
1679                write!(
1680                    f,
1681                    "{} is less than the minimum of {}",
1682                    self.placeholder, limit
1683                )
1684            }
1685            ValidationErrorKind::MaxLength { limit } => write!(
1686                f,
1687                "{} is longer than {} character{}",
1688                self.placeholder,
1689                limit,
1690                if *limit == 1 { "" } else { "s" }
1691            ),
1692            ValidationErrorKind::MinLength { limit } => write!(
1693                f,
1694                "{} is shorter than {} character{}",
1695                self.placeholder,
1696                limit,
1697                if *limit == 1 { "" } else { "s" }
1698            ),
1699            ValidationErrorKind::MaxItems { limit } => write!(
1700                f,
1701                "{} has more than {} item{}",
1702                self.placeholder,
1703                limit,
1704                if *limit == 1 { "" } else { "s" }
1705            ),
1706            ValidationErrorKind::MinItems { limit } => write!(
1707                f,
1708                "{} has less than {} item{}",
1709                self.placeholder,
1710                limit,
1711                if *limit == 1 { "" } else { "s" }
1712            ),
1713            ValidationErrorKind::MaxProperties { limit } => write!(
1714                f,
1715                "{} has more than {} propert{}",
1716                self.placeholder,
1717                limit,
1718                if *limit == 1 { "y" } else { "ies" }
1719            ),
1720            ValidationErrorKind::MinProperties { limit } => write!(
1721                f,
1722                "{} has less than {} propert{}",
1723                self.placeholder,
1724                limit,
1725                if *limit == 1 { "y" } else { "ies" }
1726            ),
1727            ValidationErrorKind::Not { schema } => {
1728                write!(f, "{} is not allowed for {}", schema, self.placeholder)
1729            }
1730            ValidationErrorKind::OneOfMultipleValid { .. } => write!(
1731                f,
1732                "{} is valid under more than one of the schemas listed in the 'oneOf' keyword",
1733                self.placeholder
1734            ),
1735            ValidationErrorKind::Pattern { pattern } => {
1736                write!(f, r#"{} does not match "{}""#, self.placeholder, pattern)
1737            }
1738            ValidationErrorKind::PropertyNames { error } => error.fmt(f),
1739            ValidationErrorKind::Required { property } => {
1740                write!(f, "{property} is a required property")
1741            }
1742            ValidationErrorKind::MultipleOf { multiple_of } => {
1743                write!(
1744                    f,
1745                    "{} is not a multiple of {}",
1746                    self.placeholder, multiple_of
1747                )
1748            }
1749            ValidationErrorKind::UnevaluatedItems { unexpected } => {
1750                write!(
1751                    f,
1752                    "Unevaluated items are not allowed ({} items)",
1753                    unexpected.len()
1754                )
1755            }
1756            ValidationErrorKind::UnevaluatedProperties { unexpected } => {
1757                f.write_str("Unevaluated properties are not allowed (")?;
1758                write_quoted_list(f, unexpected)?;
1759                write_unexpected_suffix(f, unexpected.len())
1760            }
1761            ValidationErrorKind::UniqueItems => {
1762                write!(f, "{} has non-unique elements", self.placeholder)
1763            }
1764            ValidationErrorKind::Type {
1765                kind: TypeKind::Single(type_),
1766            } => write!(f, r#"{} is not of type "{}""#, self.placeholder, type_),
1767            ValidationErrorKind::Type {
1768                kind: TypeKind::Multiple(types),
1769            } => {
1770                write!(f, "{} is not of types ", self.placeholder)?;
1771                let mut iter = types.iter();
1772                if let Some(t) = iter.next() {
1773                    f.write_char('"')?;
1774                    write!(f, "{t}")?;
1775                    f.write_char('"')?;
1776                }
1777                for t in iter {
1778                    f.write_str(", ")?;
1779                    f.write_char('"')?;
1780                    write!(f, "{t}")?;
1781                    f.write_char('"')?;
1782                }
1783                Ok(())
1784            }
1785            ValidationErrorKind::Custom { message, .. } => f.write_str(message),
1786        }
1787    }
1788}
1789
1790#[cfg(test)]
1791mod tests {
1792    use super::*;
1793    use referencing::{Registry, Resource};
1794    use serde_json::json;
1795
1796    use test_case::test_case;
1797
1798    fn owned_error(instance: Value, kind: ValidationErrorKind) -> ValidationError<'static> {
1799        ValidationError::new(
1800            Cow::Owned(instance),
1801            kind,
1802            Location::new(),
1803            Location::new(),
1804            Location::new(),
1805        )
1806    }
1807
1808    #[test]
1809    fn error_iterator_into_errors_collects_all_errors() {
1810        let iterator = ErrorIterator::from_iterator(
1811            vec![
1812                owned_error(json!(1), ValidationErrorKind::Minimum { limit: json!(2) }),
1813                owned_error(json!(3), ValidationErrorKind::Maximum { limit: json!(2) }),
1814            ]
1815            .into_iter(),
1816        );
1817        let validation_errors = iterator.into_errors();
1818        let collected: Vec<_> = validation_errors.into_iter().collect();
1819        assert_eq!(collected.len(), 2);
1820        assert_eq!(collected[0].to_string(), "1 is less than the minimum of 2");
1821        assert_eq!(
1822            collected[1].to_string(),
1823            "3 is greater than the maximum of 2"
1824        );
1825    }
1826
1827    #[test]
1828    fn validation_errors_display_reports_success() {
1829        let errors = ValidationErrors { errors: Vec::new() };
1830        assert_eq!(format!("{errors}"), "Validation succeeded");
1831    }
1832
1833    #[test]
1834    fn validation_errors_display_lists_messages() {
1835        let errors = ValidationErrors {
1836            errors: vec![
1837                owned_error(json!(1), ValidationErrorKind::Minimum { limit: json!(2) }),
1838                owned_error(json!(3), ValidationErrorKind::Maximum { limit: json!(2) }),
1839            ],
1840        };
1841        let rendered = format!("{errors}");
1842        assert!(rendered.contains("Validation errors:"));
1843        assert!(rendered.contains("01: 1 is less than the minimum of 2"));
1844        assert!(rendered.contains("02: 3 is greater than the maximum of 2"));
1845    }
1846
1847    #[test]
1848    fn validation_errors_len_and_is_empty() {
1849        let empty = ValidationErrors { errors: vec![] };
1850        assert_eq!(empty.len(), 0);
1851        assert!(empty.is_empty());
1852
1853        let errors = ValidationErrors {
1854            errors: vec![owned_error(
1855                json!(1),
1856                ValidationErrorKind::Minimum { limit: json!(2) },
1857            )],
1858        };
1859        assert_eq!(errors.len(), 1);
1860        assert!(!errors.is_empty());
1861    }
1862
1863    #[test]
1864    fn validation_errors_as_slice() {
1865        let errors = ValidationErrors {
1866            errors: vec![
1867                owned_error(json!(1), ValidationErrorKind::Minimum { limit: json!(2) }),
1868                owned_error(json!(3), ValidationErrorKind::Maximum { limit: json!(2) }),
1869            ],
1870        };
1871
1872        let slice = errors.as_slice();
1873        assert_eq!(slice.len(), 2);
1874        assert_eq!(slice[0].to_string(), "1 is less than the minimum of 2");
1875        assert_eq!(slice[1].to_string(), "3 is greater than the maximum of 2");
1876    }
1877
1878    #[test]
1879    fn validation_errors_iter() {
1880        let errors = ValidationErrors {
1881            errors: vec![
1882                owned_error(json!(1), ValidationErrorKind::Minimum { limit: json!(2) }),
1883                owned_error(json!(3), ValidationErrorKind::Maximum { limit: json!(2) }),
1884            ],
1885        };
1886
1887        let collected: Vec<_> = errors.iter().map(ValidationError::to_string).collect();
1888        assert_eq!(collected.len(), 2);
1889        assert_eq!(collected[0], "1 is less than the minimum of 2");
1890        assert_eq!(collected[1], "3 is greater than the maximum of 2");
1891    }
1892
1893    #[test]
1894    #[allow(clippy::explicit_iter_loop)]
1895    fn validation_errors_iter_mut() {
1896        let mut errors = ValidationErrors {
1897            errors: vec![owned_error(
1898                json!(1),
1899                ValidationErrorKind::Minimum { limit: json!(2) },
1900            )],
1901        };
1902
1903        // Verify we can get mutable references via iter_mut()
1904        for error in errors.iter_mut() {
1905            let _ = error.to_string();
1906        }
1907    }
1908
1909    #[test]
1910    fn validation_errors_into_iterator_by_ref() {
1911        let errors = ValidationErrors {
1912            errors: vec![owned_error(
1913                json!(1),
1914                ValidationErrorKind::Minimum { limit: json!(2) },
1915            )],
1916        };
1917
1918        let collected: Vec<_> = (&errors).into_iter().collect();
1919        assert_eq!(collected.len(), 1);
1920        // Verify errors is still usable
1921        assert_eq!(errors.len(), 1);
1922    }
1923
1924    #[test]
1925    fn validation_errors_into_iterator_by_mut_ref() {
1926        let mut errors = ValidationErrors {
1927            errors: vec![owned_error(
1928                json!(1),
1929                ValidationErrorKind::Minimum { limit: json!(2) },
1930            )],
1931        };
1932
1933        let collected: Vec<_> = (&mut errors).into_iter().collect();
1934        assert_eq!(collected.len(), 1);
1935        // Verify errors is still usable
1936        assert_eq!(errors.len(), 1);
1937    }
1938
1939    #[test]
1940    fn error_iterator_size_hint() {
1941        let vec = vec![
1942            owned_error(json!(1), ValidationErrorKind::Minimum { limit: json!(2) }),
1943            owned_error(json!(3), ValidationErrorKind::Maximum { limit: json!(2) }),
1944        ];
1945        let iterator = ErrorIterator::from_iterator(vec.into_iter());
1946        let (lower, upper) = iterator.size_hint();
1947        assert_eq!(lower, 2);
1948        assert_eq!(upper, Some(2));
1949    }
1950
1951    #[test]
1952    fn validation_errors_debug() {
1953        let errors = ValidationErrors {
1954            errors: vec![owned_error(
1955                json!(1),
1956                ValidationErrorKind::Minimum { limit: json!(2) },
1957            )],
1958        };
1959        let debug_output = format!("{errors:?}");
1960        assert!(debug_output.contains("ValidationErrors"));
1961        assert!(debug_output.contains("errors"));
1962    }
1963
1964    #[test]
1965    fn single_type_error() {
1966        let instance = json!(42);
1967        let err = ValidationError::single_type_error(
1968            Location::new(),
1969            Location::new(),
1970            Location::new(),
1971            &instance,
1972            JsonType::String,
1973        );
1974        assert_eq!(err.to_string(), r#"42 is not of type "string""#);
1975    }
1976
1977    #[test]
1978    fn multiple_types_error() {
1979        let instance = json!(42);
1980        let types = JsonTypeSet::empty()
1981            .insert(JsonType::String)
1982            .insert(JsonType::Number);
1983        let err = ValidationError::multiple_type_error(
1984            Location::new(),
1985            Location::new(),
1986            Location::new(),
1987            &instance,
1988            types,
1989        );
1990        assert_eq!(err.to_string(), r#"42 is not of types "number", "string""#);
1991    }
1992
1993    #[test_case(true, &json!({"foo": {"bar": 42}}), "/foo/bar")]
1994    #[test_case(true, &json!({"foo": "a"}), "/foo")]
1995    #[test_case(false, &json!({"foo": {"bar": 42}}), "/foo/bar")]
1996    #[test_case(false, &json!({"foo": "a"}), "/foo")]
1997    fn instance_path_properties(additional_properties: bool, instance: &Value, expected: &str) {
1998        let schema = json!(
1999            {
2000                "additionalProperties": additional_properties,
2001                "type":"object",
2002                "properties":{
2003                   "foo":{
2004                      "type":"object",
2005                      "properties":{
2006                         "bar":{
2007                            "type":"string"
2008                         }
2009                      }
2010                   }
2011                }
2012            }
2013        );
2014        let validator = crate::validator_for(&schema).unwrap();
2015        let mut result = validator.iter_errors(instance);
2016        let error = result.next().expect("validation error");
2017
2018        assert!(result.next().is_none());
2019        assert_eq!(error.instance_path().as_str(), expected);
2020    }
2021
2022    #[test_case(true, &json!([1, {"foo": ["42"]}]), "/0")]
2023    #[test_case(true, &json!(["a", {"foo": [42]}]), "/1/foo/0")]
2024    #[test_case(false, &json!([1, {"foo": ["42"]}]), "/0")]
2025    #[test_case(false, &json!(["a", {"foo": [42]}]), "/1/foo/0")]
2026    fn instance_path_properties_and_arrays(
2027        additional_items: bool,
2028        instance: &Value,
2029        expected: &str,
2030    ) {
2031        let schema = json!(
2032            {
2033                "items": additional_items,
2034                "type": "array",
2035                "prefixItems": [
2036                    {
2037                        "type": "string"
2038                    },
2039                    {
2040                        "type": "object",
2041                        "properties": {
2042                            "foo": {
2043                                "type": "array",
2044                                "prefixItems": [
2045                                    {
2046                                        "type": "string"
2047                                    }
2048                                ]
2049                            }
2050                        }
2051                    }
2052                ]
2053            }
2054        );
2055        let validator = crate::validator_for(&schema).unwrap();
2056        let mut result = validator.iter_errors(instance);
2057        let error = result.next().expect("validation error");
2058
2059        assert!(result.next().is_none());
2060        assert_eq!(error.instance_path().as_str(), expected);
2061    }
2062
2063    #[test_case(true, &json!([[1, 2, 3], [4, "5", 6], [7, 8, 9]]), "/1/1")]
2064    #[test_case(false, &json!([[1, 2, 3], [4, "5", 6], [7, 8, 9]]), "/1/1")]
2065    #[test_case(true, &json!([[1, 2, 3], [4, 5, 6], 42]), "/2")]
2066    #[test_case(false, &json!([[1, 2, 3], [4, 5, 6], 42]), "/2")]
2067    fn instance_path_nested_arrays(additional_items: bool, instance: &Value, expected: &str) {
2068        let schema = json!(
2069            {
2070                "additionalItems": additional_items,
2071                "type": "array",
2072                "items": {
2073                    "type": "array",
2074                    "items": {
2075                        "type": "integer"
2076                    }
2077                }
2078            }
2079        );
2080        let validator = crate::validator_for(&schema).unwrap();
2081        let mut result = validator.iter_errors(instance);
2082        let error = result.next().expect("validation error");
2083
2084        assert!(result.next().is_none());
2085        assert_eq!(error.instance_path().as_str(), expected);
2086    }
2087
2088    #[test_case(true, &json!([1, "a"]), "/1")]
2089    #[test_case(false, &json!([1, "a"]), "/1")]
2090    #[test_case(true, &json!(123), "")]
2091    #[test_case(false, &json!(123), "")]
2092    fn instance_path_arrays(additional_items: bool, instance: &Value, expected: &str) {
2093        let schema = json!(
2094            {
2095                "additionalItems": additional_items,
2096                "type": "array",
2097                "items": {
2098                    "type": "integer"
2099                }
2100            }
2101        );
2102        let validator = crate::validator_for(&schema).unwrap();
2103        let mut result = validator.iter_errors(instance);
2104        let error = result.next().expect("validation error");
2105
2106        assert!(result.next().is_none());
2107        assert_eq!(error.instance_path().as_str(), expected);
2108    }
2109
2110    #[test_case(
2111        json!("2023-13-45"), 
2112        ValidationErrorKind::Format {
2113            format: "date".to_string(),
2114        },
2115        "value is not a \"date\""
2116    )]
2117    #[test_case(
2118        json!("sensitive data"),
2119        ValidationErrorKind::MaxLength { limit: 5 },
2120        "value is longer than 5 characters"
2121    )]
2122    #[test_case(
2123        json!({"secret": "data", "key": "value"}),
2124        ValidationErrorKind::AdditionalProperties {
2125            unexpected: vec!["secret".to_string(), "key".to_string()] 
2126        },
2127        "Additional properties are not allowed ('secret', 'key' were unexpected)"
2128    )]
2129    #[test_case(
2130        json!(123),
2131        ValidationErrorKind::Minimum { limit: json!(456) },
2132        "value is less than the minimum of 456"
2133    )]
2134    #[test_case(
2135        json!("secret_key_123"),
2136        ValidationErrorKind::Pattern {
2137            pattern: "^[A-Z0-9]{32}$".to_string(),
2138        },
2139        "value does not match \"^[A-Z0-9]{32}$\""
2140    )]
2141    #[test_case(
2142        json!([1, 2, 2, 3]),
2143        ValidationErrorKind::UniqueItems,
2144        "value has non-unique elements"
2145    )]
2146    #[test_case(
2147        json!(123),
2148        ValidationErrorKind::Type { kind: TypeKind::Single(JsonType::String) },
2149        "value is not of type \"string\""
2150    )]
2151    fn test_masked_error_messages(instance: Value, kind: ValidationErrorKind, expected: &str) {
2152        let error = ValidationError::new(
2153            Cow::Owned(instance),
2154            kind,
2155            Location::new(),
2156            Location::new(),
2157            Location::new(),
2158        );
2159        assert_eq!(error.masked().to_string(), expected);
2160    }
2161
2162    #[test_case(
2163        json!("sensitive data"),
2164        ValidationErrorKind::MaxLength { limit: 5 },
2165        "[REDACTED]",
2166        "[REDACTED] is longer than 5 characters"
2167    )]
2168    #[test_case(
2169        json!({"password": "secret123"}),
2170        ValidationErrorKind::Type {
2171            kind: TypeKind::Single(JsonType::String)
2172        },
2173        "***",
2174        "*** is not of type \"string\""
2175    )]
2176    fn test_custom_masked_error_messages(
2177        instance: Value,
2178        kind: ValidationErrorKind,
2179        placeholder: &str,
2180        expected: &str,
2181    ) {
2182        let error = ValidationError::new(
2183            Cow::Owned(instance),
2184            kind,
2185            Location::new(),
2186            Location::new(),
2187            Location::new(),
2188        );
2189        assert_eq!(error.masked_with(placeholder).to_string(), expected);
2190    }
2191
2192    #[test]
2193    fn test_absolute_keyword_location_absent_for_schema_without_base_uri() {
2194        let schema = serde_json::json!({"type": "string"});
2195        let instance = serde_json::json!(42);
2196        let err = crate::validate(&schema, &instance).unwrap_err();
2197        assert!(err.absolute_keyword_location().is_none());
2198    }
2199
2200    #[test]
2201    fn test_absolute_keyword_location_present_for_schema_with_base_uri() {
2202        let schema = serde_json::json!({
2203            "$id": "https://example.com/schema.json",
2204            "type": "string"
2205        });
2206        let instance = serde_json::json!(42);
2207        let validator = crate::options()
2208            .build(&schema)
2209            .expect("schema should compile");
2210        let err = validator.validate(&instance).unwrap_err();
2211        let absolute_location = err
2212            .absolute_keyword_location()
2213            .expect("should have absolute keyword location");
2214        assert_eq!(
2215            absolute_location.as_str(),
2216            "https://example.com/schema.json#/type"
2217        );
2218    }
2219
2220    #[test]
2221    fn test_absolute_keyword_location_from_iter_errors() {
2222        let schema = serde_json::json!({
2223            "$id": "https://example.com/schema.json",
2224            "type": "string"
2225        });
2226        let instance = serde_json::json!(42);
2227        let validator = crate::options()
2228            .build(&schema)
2229            .expect("schema should compile");
2230        let errs: Vec<_> = validator.iter_errors(&instance).collect();
2231        assert_eq!(errs.len(), 1);
2232        let absolute_location = errs[0]
2233            .absolute_keyword_location()
2234            .expect("iter_errors should set absolute keyword location");
2235        assert_eq!(
2236            absolute_location.as_str(),
2237            "https://example.com/schema.json#/type"
2238        );
2239    }
2240
2241    #[test]
2242    fn test_absolute_keyword_location_false_schema() {
2243        let schema = serde_json::json!(false);
2244        let instance = serde_json::json!(42);
2245        let validator = crate::options()
2246            .with_base_uri("https://example.com/schema.json")
2247            .build(&schema)
2248            .expect("schema should compile");
2249        let err = validator.validate(&instance).unwrap_err();
2250        let absolute_location = err
2251            .absolute_keyword_location()
2252            .expect("false schema should have absolute keyword location");
2253        assert_eq!(
2254            absolute_location.as_str(),
2255            "https://example.com/schema.json"
2256        );
2257    }
2258
2259    #[test]
2260    fn test_absolute_keyword_location_preserved_by_to_owned() {
2261        let schema = serde_json::json!({
2262            "$id": "https://example.com/schema.json",
2263            "type": "string"
2264        });
2265        let instance = serde_json::json!(42);
2266        let validator = crate::options()
2267            .build(&schema)
2268            .expect("schema should compile");
2269        let err = validator.validate(&instance).unwrap_err();
2270        let owned = err.to_owned();
2271        let absolute_location = owned
2272            .absolute_keyword_location()
2273            .expect("to_owned should preserve absolute keyword location");
2274        assert_eq!(
2275            absolute_location.as_str(),
2276            "https://example.com/schema.json#/type"
2277        );
2278    }
2279
2280    #[test]
2281    fn test_absolute_keyword_location_external_ref() {
2282        let external = serde_json::json!({
2283            "$id": "https://example.com/string.json",
2284            "type": "string"
2285        });
2286        let schema = serde_json::json!({
2287            "$id": "https://example.com/root.json",
2288            "$ref": "https://example.com/string.json"
2289        });
2290        let instance = serde_json::json!(42);
2291        let registry = Registry::new()
2292            .add(
2293                "https://example.com/string.json",
2294                Resource::from_contents(external),
2295            )
2296            .expect("external schema should be accepted")
2297            .prepare()
2298            .expect("registry should build");
2299        let validator = crate::options()
2300            .with_registry(&registry)
2301            .build(&schema)
2302            .expect("schema should compile");
2303        let err = validator.validate(&instance).unwrap_err();
2304        let absolute_location = err
2305            .absolute_keyword_location()
2306            .expect("external ref error should have absolute keyword location");
2307        // Error originates in the external schema, not the root
2308        assert_eq!(
2309            absolute_location.as_str(),
2310            "https://example.com/string.json#/type"
2311        );
2312    }
2313}