jsonschema-rs/jsonschema/src/error.rs

1153 lines
39 KiB
Rust

//! Error types
use crate::{
paths::JSONPointer,
primitive_type::{PrimitiveType, PrimitiveTypesBitMap},
resolver::SchemaResolverError,
};
use serde_json::{Map, Number, Value};
use std::{
borrow::Cow,
error, fmt,
fmt::Formatter,
io,
iter::{empty, once},
str::Utf8Error,
string::FromUtf8Error,
};
use url::Url;
/// An error that can occur during validation.
#[derive(Debug)]
pub struct ValidationError<'a> {
/// Value of the property that failed validation.
pub instance: Cow<'a, Value>,
/// Type of validation error.
pub kind: ValidationErrorKind,
/// Path to the value that failed validation.
pub instance_path: JSONPointer,
/// Path to the JSON Schema keyword that failed validation.
pub schema_path: JSONPointer,
}
/// An iterator over instances of `ValidationError` that represent validation error for the
/// input instance.
///
/// # Examples
///
/// ```rust
/// use jsonschema::JSONSchema;
/// use serde_json::json;
///
/// let schema = json!({"maxLength": 5});
/// let instance = json!("foo");
/// if let Ok(compiled) = JSONSchema::compile(&schema) {
/// let result = compiled.validate(&instance);
/// if let Err(errors) = result {
/// for error in errors {
/// println!("Validation error: {}", error)
/// }
/// }
/// }
/// ```
pub type ErrorIterator<'a> = Box<dyn Iterator<Item = ValidationError<'a>> + Sync + Send + 'a>;
// Empty iterator means no error happened
pub(crate) fn no_error<'a>() -> ErrorIterator<'a> {
Box::new(empty())
}
// A wrapper for one error
pub(crate) fn error(instance: ValidationError) -> ErrorIterator {
Box::new(once(instance))
}
/// Kinds of errors that may happen during validation
#[derive(Debug)]
#[allow(missing_docs)]
pub enum ValidationErrorKind {
/// The input array contain more items than expected.
AdditionalItems { limit: usize },
/// Unexpected properties.
AdditionalProperties { unexpected: Vec<String> },
/// The input value is not valid under any of the schemas listed in the 'anyOf' keyword.
AnyOf,
/// Results from a [`fancy_regex::Error::BacktrackLimitExceeded`] variant when matching
BacktrackLimitExceeded { error: fancy_regex::Error },
/// The input value doesn't match expected constant.
Constant { expected_value: Value },
/// The input array doesn't contain items conforming to the specified schema.
Contains,
/// The input value does not respect the defined contentEncoding
ContentEncoding { content_encoding: String },
/// The input value does not respect the defined contentMediaType
ContentMediaType { content_media_type: String },
/// The input value doesn't match any of specified options.
Enum { options: Value },
/// Value is too large.
ExclusiveMaximum { limit: Value },
/// Value is too small.
ExclusiveMinimum { limit: Value },
/// Everything is invalid for `false` schema.
FalseSchema,
/// If the referenced file is not found during ref resolution.
FileNotFound { error: io::Error },
/// When the input doesn't match to the specified format.
Format { format: &'static str },
/// May happen in `contentEncoding` validation if `base64` encoded data is invalid.
FromUtf8 { error: FromUtf8Error },
/// Invalid UTF-8 string during percent encoding when resolving happens
Utf8 { error: Utf8Error },
/// May happen during ref resolution when remote document is not a valid JSON.
JSONParse { error: serde_json::Error },
/// `ref` value is not valid.
InvalidReference { reference: String },
/// Invalid URL, e.g. invalid port number or IP address
InvalidURL { error: url::ParseError },
/// Too many items in an array.
MaxItems { limit: u64 },
/// Value is too large.
Maximum { limit: Value },
/// String is too long.
MaxLength { limit: u64 },
/// Too many properties in an object.
MaxProperties { limit: u64 },
/// Too few items in an array.
MinItems { limit: u64 },
/// Value is too small.
Minimum { limit: Value },
/// String is too short.
MinLength { limit: u64 },
/// Not enough properties in an object.
MinProperties { limit: u64 },
/// When some number is not a multiple of another number.
MultipleOf { multiple_of: f64 },
/// Negated schema failed validation.
Not { schema: Value },
/// The given schema is valid under more than one of the schemas listed in the 'oneOf' keyword.
OneOfMultipleValid,
/// The given schema is not valid under any of the schemas listed in the 'oneOf' keyword.
OneOfNotValid,
/// When the input doesn't match to a pattern.
Pattern { pattern: String },
/// Object property names are invalid.
PropertyNames {
error: Box<ValidationError<'static>>,
},
/// When a required property is missing.
Required { property: Value },
/// Resolved schema failed to compile.
Schema,
/// When the input value doesn't match one or multiple required types.
Type { kind: TypeKind },
/// Unexpected properties.
UnevaluatedProperties { unexpected: Vec<String> },
/// When the input array has non-unique elements.
UniqueItems,
/// Reference contains unknown scheme.
UnknownReferenceScheme { scheme: String },
/// Error during schema ref resolution.
Resolver {
/// The url that was tried to be resolved.
url: Url,
/// The resolution error.
error: SchemaResolverError,
},
}
#[derive(Debug)]
#[allow(missing_docs)]
pub enum TypeKind {
Single(PrimitiveType),
Multiple(PrimitiveTypesBitMap),
}
/// Shortcuts for creation of specific error kinds.
impl<'a> ValidationError<'a> {
pub(crate) fn into_owned(self) -> ValidationError<'static> {
ValidationError {
instance_path: self.instance_path.clone(),
instance: Cow::Owned(self.instance.into_owned()),
kind: self.kind,
schema_path: self.schema_path,
}
}
pub(crate) const fn additional_items(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
limit: usize,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::AdditionalItems { limit },
schema_path,
}
}
pub(crate) const fn additional_properties(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
unexpected: Vec<String>,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::AdditionalProperties { unexpected },
schema_path,
}
}
pub(crate) const fn any_of(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::AnyOf,
schema_path,
}
}
pub(crate) const fn backtrack_limit(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
error: fancy_regex::Error,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::BacktrackLimitExceeded { error },
schema_path,
}
}
pub(crate) fn constant_array(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
expected_value: &[Value],
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Constant {
expected_value: Value::Array(expected_value.to_vec()),
},
schema_path,
}
}
pub(crate) const fn constant_boolean(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
expected_value: bool,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Constant {
expected_value: Value::Bool(expected_value),
},
schema_path,
}
}
pub(crate) const fn constant_null(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Constant {
expected_value: Value::Null,
},
schema_path,
}
}
pub(crate) fn constant_number(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
expected_value: &Number,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Constant {
expected_value: Value::Number(expected_value.clone()),
},
schema_path,
}
}
pub(crate) fn constant_object(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
expected_value: &Map<String, Value>,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Constant {
expected_value: Value::Object(expected_value.clone()),
},
schema_path,
}
}
pub(crate) fn constant_string(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
expected_value: &str,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Constant {
expected_value: Value::String(expected_value.to_string()),
},
schema_path,
}
}
pub(crate) const fn contains(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Contains,
schema_path,
}
}
pub(crate) fn content_encoding(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
encoding: &str,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::ContentEncoding {
content_encoding: encoding.to_string(),
},
schema_path,
}
}
pub(crate) fn content_media_type(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
media_type: &str,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::ContentMediaType {
content_media_type: media_type.to_string(),
},
schema_path,
}
}
pub(crate) fn enumeration(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
options: &Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Enum {
options: options.clone(),
},
schema_path,
}
}
pub(crate) const fn exclusive_maximum(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
limit: Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::ExclusiveMaximum { limit },
schema_path,
}
}
pub(crate) const fn exclusive_minimum(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
limit: Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::ExclusiveMinimum { limit },
schema_path,
}
}
pub(crate) const fn false_schema(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::FalseSchema,
schema_path,
}
}
pub(crate) fn file_not_found(error: io::Error) -> ValidationError<'a> {
ValidationError {
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::FileNotFound { error },
schema_path: JSONPointer::default(),
}
}
pub(crate) const fn format(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
format: &'static str,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Format { format },
schema_path,
}
}
pub(crate) fn from_utf8(error: FromUtf8Error) -> ValidationError<'a> {
ValidationError {
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::FromUtf8 { error },
schema_path: JSONPointer::default(),
}
}
pub(crate) fn json_parse(error: serde_json::Error) -> ValidationError<'a> {
ValidationError {
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::JSONParse { error },
schema_path: JSONPointer::default(),
}
}
pub(crate) fn invalid_reference(reference: String) -> ValidationError<'a> {
ValidationError {
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::InvalidReference { reference },
schema_path: JSONPointer::default(),
}
}
pub(crate) fn invalid_url(error: url::ParseError) -> ValidationError<'a> {
ValidationError {
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::InvalidURL { error },
schema_path: JSONPointer::default(),
}
}
pub(crate) const fn max_items(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
limit: u64,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::MaxItems { limit },
schema_path,
}
}
pub(crate) const fn maximum(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
limit: Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Maximum { limit },
schema_path,
}
}
pub(crate) const fn max_length(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
limit: u64,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::MaxLength { limit },
schema_path,
}
}
pub(crate) const fn max_properties(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
limit: u64,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::MaxProperties { limit },
schema_path,
}
}
pub(crate) const fn min_items(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
limit: u64,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::MinItems { limit },
schema_path,
}
}
pub(crate) const fn minimum(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
limit: Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Minimum { limit },
schema_path,
}
}
pub(crate) const fn min_length(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
limit: u64,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::MinLength { limit },
schema_path,
}
}
pub(crate) const fn min_properties(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
limit: u64,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::MinProperties { limit },
schema_path,
}
}
pub(crate) const fn multiple_of(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
multiple_of: f64,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::MultipleOf { multiple_of },
schema_path,
}
}
pub(crate) const fn not(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
schema: Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Not { schema },
schema_path,
}
}
pub(crate) const fn one_of_multiple_valid(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::OneOfMultipleValid,
schema_path,
}
}
pub(crate) const fn one_of_not_valid(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::OneOfNotValid,
schema_path,
}
}
pub(crate) const fn pattern(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
pattern: String,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Pattern { pattern },
schema_path,
}
}
pub(crate) fn property_names(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
error: ValidationError<'a>,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::PropertyNames {
error: Box::new(error.into_owned()),
},
schema_path,
}
}
pub(crate) const fn required(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
property: Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Required { property },
schema_path,
}
}
pub(crate) fn null_schema() -> ValidationError<'a> {
ValidationError {
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::Schema,
schema_path: JSONPointer::default(),
}
}
pub(crate) const fn single_type_error(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
type_name: PrimitiveType,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Type {
kind: TypeKind::Single(type_name),
},
schema_path,
}
}
pub(crate) const fn multiple_type_error(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
types: PrimitiveTypesBitMap,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::Type {
kind: TypeKind::Multiple(types),
},
schema_path,
}
}
pub(crate) const fn unevaluated_properties(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
unexpected: Vec<String>,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::UnevaluatedProperties { unexpected },
schema_path,
}
}
pub(crate) const fn unique_items(
schema_path: JSONPointer,
instance_path: JSONPointer,
instance: &'a Value,
) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
kind: ValidationErrorKind::UniqueItems,
schema_path,
}
}
pub(crate) fn utf8(error: Utf8Error) -> ValidationError<'a> {
ValidationError {
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::Utf8 { error },
schema_path: JSONPointer::default(),
}
}
pub(crate) fn resolver(url: Url, error: SchemaResolverError) -> ValidationError<'a> {
ValidationError {
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::Resolver { url, error },
schema_path: JSONPointer::default(),
}
}
}
impl error::Error for ValidationError<'_> {}
impl From<serde_json::Error> for ValidationError<'_> {
#[inline]
fn from(err: serde_json::Error) -> Self {
ValidationError::json_parse(err)
}
}
impl From<io::Error> for ValidationError<'_> {
#[inline]
fn from(err: io::Error) -> Self {
ValidationError::file_not_found(err)
}
}
impl From<FromUtf8Error> for ValidationError<'_> {
#[inline]
fn from(err: FromUtf8Error) -> Self {
ValidationError::from_utf8(err)
}
}
impl From<Utf8Error> for ValidationError<'_> {
#[inline]
fn from(err: Utf8Error) -> Self {
ValidationError::utf8(err)
}
}
impl From<url::ParseError> for ValidationError<'_> {
#[inline]
fn from(err: url::ParseError) -> Self {
ValidationError::invalid_url(err)
}
}
/// Textual representation of various validation errors.
impl fmt::Display for ValidationError<'_> {
#[allow(clippy::too_many_lines)] // The function is long but it does formatting only
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match &self.kind {
ValidationErrorKind::Schema => f.write_str("Schema error"),
ValidationErrorKind::JSONParse { error } => error.fmt(f),
ValidationErrorKind::Resolver { url, error } => {
write!(f, "failed to resolve {}: {}", url, error)
}
ValidationErrorKind::FileNotFound { error } => error.fmt(f),
ValidationErrorKind::InvalidURL { error } => error.fmt(f),
ValidationErrorKind::BacktrackLimitExceeded { error } => error.fmt(f),
ValidationErrorKind::UnknownReferenceScheme { scheme } => {
write!(f, "Unknown scheme: {}", scheme)
}
ValidationErrorKind::Format { format } => {
write!(f, r#"{} is not a "{}""#, self.instance, format)
}
ValidationErrorKind::AdditionalItems { limit } => {
// It's safe to unwrap here as ValidationErrorKind::AdditionalItems is reported only in
// case of arrays with more items than expected
let extras: Vec<&Value> = self
.instance
.as_array()
.expect("Always valid")
.iter()
.skip(*limit)
.collect();
let verb = {
if extras.len() == 1 {
"was"
} else {
"were"
}
};
write!(
f,
"Additional items are not allowed ({} {} unexpected)",
extras
.iter()
.map(ToString::to_string)
.collect::<Vec<String>>()
.join(", "),
verb
)
}
ValidationErrorKind::AdditionalProperties { unexpected } => {
let verb = {
if unexpected.len() == 1 {
"was"
} else {
"were"
}
};
write!(
f,
"Additional properties are not allowed ({} {} unexpected)",
unexpected
.iter()
.map(|x| format!("'{}'", x))
.collect::<Vec<String>>()
.join(", "),
verb
)
}
ValidationErrorKind::AnyOf => write!(
f,
"{} is not valid under any of the schemas listed in the 'anyOf' keyword",
self.instance
),
ValidationErrorKind::OneOfNotValid => write!(
f,
"{} is not valid under any of the schemas listed in the 'oneOf' keyword",
self.instance
),
ValidationErrorKind::Contains => write!(
f,
"None of {} are valid under the given schema",
self.instance
),
ValidationErrorKind::Constant { expected_value } => {
write!(f, "{} was expected", expected_value)
}
ValidationErrorKind::ContentEncoding { content_encoding } => {
write!(
f,
r#"{} is not compliant with "{}" content encoding"#,
self.instance, content_encoding
)
}
ValidationErrorKind::ContentMediaType { content_media_type } => {
write!(
f,
r#"{} is not compliant with "{}" media type"#,
self.instance, content_media_type
)
}
ValidationErrorKind::FromUtf8 { error } => error.fmt(f),
ValidationErrorKind::Utf8 { error } => error.fmt(f),
ValidationErrorKind::Enum { options } => {
write!(f, "{} is not one of {}", self.instance, options)
}
ValidationErrorKind::ExclusiveMaximum { limit } => write!(
f,
"{} is greater than or equal to the maximum of {}",
self.instance, limit
),
ValidationErrorKind::ExclusiveMinimum { limit } => write!(
f,
"{} is less than or equal to the minimum of {}",
self.instance, limit
),
ValidationErrorKind::FalseSchema => {
write!(f, "False schema does not allow {}", self.instance)
}
ValidationErrorKind::InvalidReference { reference } => {
write!(f, "Invalid reference: {}", reference)
}
ValidationErrorKind::Maximum { limit } => write!(
f,
"{} is greater than the maximum of {}",
self.instance, limit
),
ValidationErrorKind::Minimum { limit } => {
write!(f, "{} is less than the minimum of {}", self.instance, limit)
}
ValidationErrorKind::MaxLength { limit } => write!(
f,
"{} is longer than {} character{}",
self.instance,
limit,
if *limit == 1 { "" } else { "s" }
),
ValidationErrorKind::MinLength { limit } => write!(
f,
"{} is shorter than {} character{}",
self.instance,
limit,
if *limit == 1 { "" } else { "s" }
),
ValidationErrorKind::MaxItems { limit } => write!(
f,
"{} has more than {} item{}",
self.instance,
limit,
if *limit == 1 { "" } else { "s" }
),
ValidationErrorKind::MinItems { limit } => write!(
f,
"{} has less than {} item{}",
self.instance,
limit,
if *limit == 1 { "" } else { "s" }
),
ValidationErrorKind::MaxProperties { limit } => write!(
f,
"{} has more than {} propert{}",
self.instance,
limit,
if *limit == 1 { "y" } else { "ies" }
),
ValidationErrorKind::MinProperties { limit } => write!(
f,
"{} has less than {} propert{}",
self.instance,
limit,
if *limit == 1 { "y" } else { "ies" }
),
ValidationErrorKind::Not { schema } => {
write!(f, "{} is not allowed for {}", schema, self.instance)
}
ValidationErrorKind::OneOfMultipleValid => write!(
f,
"{} is valid under more than one of the schemas listed in the 'oneOf' keyword",
self.instance
),
ValidationErrorKind::Pattern { pattern } => {
write!(f, r#"{} does not match "{}""#, self.instance, pattern)
}
ValidationErrorKind::PropertyNames { error } => error.fmt(f),
ValidationErrorKind::Required { property } => {
write!(f, "{} is a required property", property)
}
ValidationErrorKind::MultipleOf { multiple_of } => {
write!(f, "{} is not a multiple of {}", self.instance, multiple_of)
}
ValidationErrorKind::UnevaluatedProperties { unexpected } => {
let verb = {
if unexpected.len() == 1 {
"was"
} else {
"were"
}
};
write!(
f,
"Unevaluated properties are not allowed ({} {} unexpected)",
unexpected
.iter()
.map(|x| format!("'{}'", x))
.collect::<Vec<String>>()
.join(", "),
verb
)
}
ValidationErrorKind::UniqueItems => {
write!(f, "{} has non-unique elements", self.instance)
}
ValidationErrorKind::Type {
kind: TypeKind::Single(type_),
} => write!(f, r#"{} is not of type "{}""#, self.instance, type_),
ValidationErrorKind::Type {
kind: TypeKind::Multiple(types),
} => write!(
f,
"{} is not of types {}",
self.instance,
types
.into_iter()
.map(|t| format!(r#""{}""#, t))
.collect::<Vec<String>>()
.join(", ")
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{paths::PathChunk, JSONSchema};
use serde_json::json;
use test_case::test_case;
#[test]
fn single_type_error() {
let instance = json!(42);
let err = ValidationError::single_type_error(
JSONPointer::default(),
JSONPointer::default(),
&instance,
PrimitiveType::String,
);
assert_eq!(err.to_string(), r#"42 is not of type "string""#)
}
#[test]
fn multiple_types_error() {
let instance = json!(42);
let err = ValidationError::multiple_type_error(
JSONPointer::default(),
JSONPointer::default(),
&instance,
vec![PrimitiveType::String, PrimitiveType::Number].into(),
);
assert_eq!(err.to_string(), r#"42 is not of types "number", "string""#)
}
#[test_case(true, &json!({"foo": {"bar": 42}}), &["foo", "bar"])]
#[test_case(true, &json!({"foo": "a"}), &["foo"])]
#[test_case(false, &json!({"foo": {"bar": 42}}), &["foo", "bar"])]
#[test_case(false, &json!({"foo": "a"}), &["foo"])]
fn instance_path_properties(additional_properties: bool, instance: &Value, expected: &[&str]) {
let schema = json!(
{
"additionalProperties": additional_properties,
"type":"object",
"properties":{
"foo":{
"type":"object",
"properties":{
"bar":{
"type":"string"
}
}
}
}
}
);
let compiled = JSONSchema::compile(&schema).unwrap();
let mut result = compiled.validate(instance).expect_err("error iterator");
let error = result.next().expect("validation error");
assert!(result.next().is_none());
assert_eq!(error.instance_path, JSONPointer::from(expected));
}
#[test_case(true, &json!([1, {"foo": ["42"]}]), &[PathChunk::Index(0)])]
#[test_case(true, &json!(["a", {"foo": [42]}]), &[PathChunk::Index(1), PathChunk::Property("foo".into()), PathChunk::Index(0)])]
#[test_case(false, &json!([1, {"foo": ["42"]}]), &[PathChunk::Index(0)])]
#[test_case(false, &json!(["a", {"foo": [42]}]), &[PathChunk::Index(1), PathChunk::Property("foo".into()), PathChunk::Index(0)])]
fn instance_path_properties_and_arrays(
additional_items: bool,
instance: &Value,
expected: &[PathChunk],
) {
let schema = json!(
{
"additionalItems": additional_items,
"type": "array",
"items": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"foo": {
"type": "array",
"items": [
{
"type": "string"
}
]
}
}
}
]
}
);
let compiled = JSONSchema::compile(&schema).unwrap();
let mut result = compiled.validate(instance).expect_err("error iterator");
let error = result.next().expect("validation error");
assert!(result.next().is_none());
assert_eq!(error.instance_path, JSONPointer::from(expected));
}
#[test_case(true, &json!([[1, 2, 3], [4, "5", 6], [7, 8, 9]]), &[PathChunk::Index(1), PathChunk::Index(1)])]
#[test_case(false, &json!([[1, 2, 3], [4, "5", 6], [7, 8, 9]]), &[PathChunk::Index(1), PathChunk::Index(1)])]
#[test_case(true, &json!([[1, 2, 3], [4, 5, 6], 42]), &[PathChunk::Index(2)])]
#[test_case(false, &json!([[1, 2, 3], [4, 5, 6], 42]), &[PathChunk::Index(2)])]
fn instance_path_nested_arrays(
additional_items: bool,
instance: &Value,
expected: &[PathChunk],
) {
let schema = json!(
{
"additionalItems": additional_items,
"type": "array",
"items": {
"type": "array",
"items": {
"type": "integer"
}
}
}
);
let compiled = JSONSchema::compile(&schema).unwrap();
let mut result = compiled.validate(instance).expect_err("error iterator");
let error = result.next().expect("validation error");
assert!(result.next().is_none());
assert_eq!(error.instance_path, JSONPointer::from(expected));
}
#[test_case(true, &json!([1, "a"]), &[PathChunk::Index(1)])]
#[test_case(false, &json!([1, "a"]), &[PathChunk::Index(1)])]
#[test_case(true, &json!(123), &[])]
#[test_case(false, &json!(123), &[])]
fn instance_path_arrays(additional_items: bool, instance: &Value, expected: &[PathChunk]) {
let schema = json!(
{
"additionalItems": additional_items,
"type": "array",
"items": {
"type": "integer"
}
}
);
let compiled = JSONSchema::compile(&schema).unwrap();
let mut result = compiled.validate(instance).expect_err("error iterator");
let error = result.next().expect("validation error");
assert!(result.next().is_none());
assert_eq!(error.instance_path, JSONPointer::from(expected));
}
}