chore: Convert `ValidationError.instance_path` to a separate struct

This commit is contained in:
Dmitry Dygalo 2021-04-27 13:40:11 +02:00 committed by Dmitry Dygalo
parent bdbe656a99
commit 674da91b63
45 changed files with 273 additions and 175 deletions

View File

@ -7,6 +7,7 @@
- The `propertyNames` validator now contains the parent object in its `instance` attribute instead of individual properties as strings.
- Improved error message for the `additionalProperties` validator. After - `Additional properties are not allowed ('faz' was unexpected)`, before - `False schema does not allow '"faz"'`.
- The `additionalProperties` validator emits a single error for all unexpected properties instead of separate errors for each unexpected property.
- `ValidationError.instance_path` is now a separate struct, that can be transformed to `Vec<String>` or JSON Pointer of type `String`.
### Fixed

View File

@ -33,7 +33,7 @@ fn main() -> Result<(), CompilationError> {
if let Err(errors) = result {
for error in errors {
println!("Validation error: {}", error);
println!("Instance path: {:?}", error.instance_path);
println!("Instance path: {}", error.instance_path);
}
}
Ok(())
@ -41,6 +41,7 @@ fn main() -> Result<(), CompilationError> {
```
Each error has an `instance_path` attribute that indicates the path to the erroneous part within the validated instance.
It could be transformed to JSON Pointer via `.to_string()` or to `Vec<String>` via `.into_vec()`.
If you only need to know whether document is valid or not (which is faster):
@ -95,7 +96,7 @@ Ratios are given against compiled `JSONSchema` using its `validate`. The `is_val
| ------------- | ----------------------- | ----------------------- | --------------------- | ---------------------- |
| Big valid | - | 95.008 ms (**x12.27**) | 7.74 ms | 5.785 ms (**x0.74**) |
| Small valid | 2.04 us (**x4.18**) | 3.67 us (**x7.53**) | 487.38 ns | 113.3 ns (**x0.23**) |
| Small invalid | 397.52 ns (**x0.60**) | 3.73 us (**x5.67**) | 657.49 ns | 5.53 ns (**x0.008**) |
| Small invalid | 397.52 ns (**x0.64**) | 3.73 us (**x6.02**) | 619.32 ns | 5.53 ns (**x0.008**) |
Unfortunately, `jsonschema_valid` mistakenly considers the Kubernetes Open API schema as invalid and therefore can't be compared with other libraries in this case.

View File

@ -32,6 +32,7 @@ num-cmp = ">= 0.1"
idna = ">= 0.2"
ahash = "0.7"
structopt = { version = ">= 0.3", optional = true }
itoa = "0.4"
[dev-dependencies]
criterion = ">= 0.1"

View File

@ -7,7 +7,8 @@ pub(crate) mod options;
use crate::{
error::{CompilationError, ErrorIterator},
keywords,
keywords::{InstancePath, Validators},
keywords::Validators,
paths::InstancePath,
resolver::Resolver,
};
use context::CompilationContext;

View File

@ -1,5 +1,8 @@
//! Error types
use crate::primitive_type::{PrimitiveType, PrimitiveTypesBitMap};
use crate::{
paths::JSONPointer,
primitive_type::{PrimitiveType, PrimitiveTypesBitMap},
};
use serde_json::{Map, Number, Value};
use std::{
borrow::Cow,
@ -51,7 +54,7 @@ pub struct ValidationError<'a> {
/// Type of validation error
pub kind: ValidationErrorKind,
/// Path of the property that failed validation
pub instance_path: Vec<String>,
pub instance_path: JSONPointer,
}
/// An iterator over instances of `ValidationError` that represent validation error for the
@ -188,7 +191,7 @@ impl<'a> ValidationError<'a> {
}
pub(crate) fn additional_items(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
limit: usize,
) -> ValidationError<'a> {
@ -199,7 +202,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn additional_properties(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
unexpected: Vec<String>,
) -> ValidationError<'a> {
@ -209,7 +212,7 @@ impl<'a> ValidationError<'a> {
kind: ValidationErrorKind::AdditionalProperties { unexpected },
}
}
pub(crate) fn any_of(instance_path: Vec<String>, instance: &'a Value) -> ValidationError<'a> {
pub(crate) fn any_of(instance_path: JSONPointer, instance: &'a Value) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
@ -217,7 +220,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn constant_array(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
expected_value: &[Value],
) -> ValidationError<'a> {
@ -230,7 +233,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn constant_boolean(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
expected_value: bool,
) -> ValidationError<'a> {
@ -243,7 +246,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn constant_null(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
) -> ValidationError<'a> {
ValidationError {
@ -255,7 +258,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn constant_number(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
expected_value: &Number,
) -> ValidationError<'a> {
@ -268,7 +271,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn constant_object(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
expected_value: &Map<String, Value>,
) -> ValidationError<'a> {
@ -281,7 +284,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn constant_string(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
expected_value: &str,
) -> ValidationError<'a> {
@ -293,7 +296,7 @@ impl<'a> ValidationError<'a> {
},
}
}
pub(crate) fn contains(instance_path: Vec<String>, instance: &'a Value) -> ValidationError<'a> {
pub(crate) fn contains(instance_path: JSONPointer, instance: &'a Value) -> ValidationError<'a> {
ValidationError {
instance_path,
instance: Cow::Borrowed(instance),
@ -301,7 +304,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn content_encoding(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
encoding: &str,
) -> ValidationError<'a> {
@ -314,7 +317,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn content_media_type(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
media_type: &str,
) -> ValidationError<'a> {
@ -327,7 +330,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn enumeration(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
options: &Value,
) -> ValidationError<'a> {
@ -340,7 +343,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn exclusive_maximum(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
limit: f64,
) -> ValidationError<'a> {
@ -351,7 +354,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn exclusive_minimum(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
limit: f64,
) -> ValidationError<'a> {
@ -362,7 +365,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn false_schema(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
) -> ValidationError<'a> {
ValidationError {
@ -373,13 +376,13 @@ impl<'a> ValidationError<'a> {
}
pub(crate) fn file_not_found(error: io::Error) -> ValidationError<'a> {
ValidationError {
instance_path: Vec::new(),
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::FileNotFound { error },
}
}
pub(crate) fn format(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
format: &'static str,
) -> ValidationError<'a> {
@ -391,34 +394,34 @@ impl<'a> ValidationError<'a> {
}
pub(crate) fn from_utf8(error: FromUtf8Error) -> ValidationError<'a> {
ValidationError {
instance_path: Vec::new(),
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::FromUtf8 { error },
}
}
pub(crate) fn json_parse(error: serde_json::Error) -> ValidationError<'a> {
ValidationError {
instance_path: Vec::new(),
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::JSONParse { error },
}
}
pub(crate) fn invalid_reference(reference: String) -> ValidationError<'a> {
ValidationError {
instance_path: Vec::new(),
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::InvalidReference { reference },
}
}
pub(crate) fn invalid_url(error: url::ParseError) -> ValidationError<'a> {
ValidationError {
instance_path: Vec::new(),
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::InvalidURL { error },
}
}
pub(crate) fn max_items(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
limit: u64,
) -> ValidationError<'a> {
@ -429,7 +432,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn maximum(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
limit: f64,
) -> ValidationError<'a> {
@ -440,7 +443,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn max_length(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
limit: u64,
) -> ValidationError<'a> {
@ -451,7 +454,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn max_properties(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
limit: u64,
) -> ValidationError<'a> {
@ -462,7 +465,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn min_items(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
limit: u64,
) -> ValidationError<'a> {
@ -473,7 +476,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn minimum(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
limit: f64,
) -> ValidationError<'a> {
@ -484,7 +487,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn min_length(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
limit: u64,
) -> ValidationError<'a> {
@ -495,7 +498,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn min_properties(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
limit: u64,
) -> ValidationError<'a> {
@ -506,7 +509,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn multiple_of(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
multiple_of: f64,
) -> ValidationError<'a> {
@ -517,7 +520,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn not(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
schema: Value,
) -> ValidationError<'a> {
@ -528,7 +531,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn one_of_multiple_valid(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
) -> ValidationError<'a> {
ValidationError {
@ -538,7 +541,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn one_of_not_valid(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
) -> ValidationError<'a> {
ValidationError {
@ -548,7 +551,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn pattern(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
pattern: String,
) -> ValidationError<'a> {
@ -559,7 +562,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn property_names(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
error: ValidationError<'a>,
) -> ValidationError<'a> {
@ -572,7 +575,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn required(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
property: String,
) -> ValidationError<'a> {
@ -585,20 +588,20 @@ impl<'a> ValidationError<'a> {
#[cfg(any(feature = "reqwest", test))]
pub(crate) fn reqwest(error: reqwest::Error) -> ValidationError<'a> {
ValidationError {
instance_path: Vec::new(),
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::Reqwest { error },
}
}
pub(crate) fn schema() -> ValidationError<'a> {
ValidationError {
instance_path: Vec::new(),
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::Schema,
}
}
pub(crate) fn single_type_error(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
type_name: PrimitiveType,
) -> ValidationError<'a> {
@ -611,7 +614,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn multiple_type_error(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
types: PrimitiveTypesBitMap,
) -> ValidationError<'a> {
@ -624,7 +627,7 @@ impl<'a> ValidationError<'a> {
}
}
pub(crate) fn unique_items(
instance_path: Vec<String>,
instance_path: JSONPointer,
instance: &'a Value,
) -> ValidationError<'a> {
ValidationError {
@ -635,14 +638,14 @@ impl<'a> ValidationError<'a> {
}
pub(crate) fn unknown_reference_scheme(scheme: String) -> ValidationError<'a> {
ValidationError {
instance_path: Vec::new(),
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::UnknownReferenceScheme { scheme },
}
}
pub(crate) fn utf8(error: Utf8Error) -> ValidationError<'a> {
ValidationError {
instance_path: Vec::new(),
instance_path: JSONPointer::default(),
instance: Cow::Owned(Value::Null),
kind: ValidationErrorKind::Utf8 { error },
}
@ -902,14 +905,18 @@ impl fmt::Display for ValidationError<'_> {
#[cfg(test)]
mod tests {
use super::*;
use crate::JSONSchema;
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(vec![], &instance, PrimitiveType::String);
let err = ValidationError::single_type_error(
JSONPointer::default(),
&instance,
PrimitiveType::String,
);
assert_eq!(err.to_string(), "'42' is not of type 'string'")
}
@ -917,7 +924,7 @@ mod tests {
fn multiple_types_error() {
let instance = json!(42);
let err = ValidationError::multiple_type_error(
vec![],
JSONPointer::default(),
&instance,
vec![PrimitiveType::String, PrimitiveType::Number].into(),
);
@ -950,17 +957,17 @@ mod tests {
let error = result.next().expect("validation error");
assert!(result.next().is_none());
assert_eq!(error.instance_path, expected);
assert_eq!(error.instance_path, JSONPointer::from(expected));
}
#[test_case(true, &json!([1, {"foo": ["42"]}]), &["0"])]
#[test_case(true, &json!(["a", {"foo": [42]}]), &["1", "foo", "0"])]
#[test_case(false, &json!([1, {"foo": ["42"]}]), &["0"])]
#[test_case(false, &json!(["a", {"foo": [42]}]), &["1", "foo", "0"])]
#[test_case(true, &json!([1, {"foo": ["42"]}]), &[PathChunk::Index(0)])]
#[test_case(true, &json!(["a", {"foo": [42]}]), &[PathChunk::Index(1), PathChunk::Name("foo".to_string()), PathChunk::Index(0)])]
#[test_case(false, &json!([1, {"foo": ["42"]}]), &[PathChunk::Index(0)])]
#[test_case(false, &json!(["a", {"foo": [42]}]), &[PathChunk::Index(1), PathChunk::Name("foo".to_string()), PathChunk::Index(0)])]
fn instance_path_properties_and_arrays(
additional_items: bool,
instance: &Value,
expected: &[&str],
expected: &[PathChunk],
) {
let schema = json!(
{
@ -991,14 +998,18 @@ mod tests {
let error = result.next().expect("validation error");
assert!(result.next().is_none());
assert_eq!(error.instance_path, expected);
assert_eq!(error.instance_path, JSONPointer::from(expected));
}
#[test_case(true, &json!([[1, 2, 3], [4, "5", 6], [7, 8, 9]]), &["1", "1"])]
#[test_case(false, &json!([[1, 2, 3], [4, "5", 6], [7, 8, 9]]), &["1", "1"])]
#[test_case(true, &json!([[1, 2, 3], [4, 5, 6], 42]), &["2"])]
#[test_case(false, &json!([[1, 2, 3], [4, 5, 6], 42]), &["2"])]
fn instance_path_nested_arrays(additional_items: bool, instance: &Value, expected: &[&str]) {
#[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,
@ -1016,14 +1027,14 @@ mod tests {
let error = result.next().expect("validation error");
assert!(result.next().is_none());
assert_eq!(error.instance_path, expected);
assert_eq!(error.instance_path, JSONPointer::from(expected));
}
#[test_case(true, &json!([1, "a"]), &["1"])]
#[test_case(false, &json!([1, "a"]), &["1"])]
#[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: &[&str]) {
fn instance_path_arrays(additional_items: bool, instance: &Value, expected: &[PathChunk]) {
let schema = json!(
{
"additionalItems": additional_items,
@ -1038,6 +1049,6 @@ mod tests {
let error = result.next().expect("validation error");
assert!(result.next().is_none());
assert_eq!(error.instance_path, expected);
assert_eq!(error.instance_path, JSONPointer::from(expected));
}
}

View File

@ -3,8 +3,9 @@ use crate::{
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{
boolean::{FalseValidator, TrueValidator},
format_validators, CompilationResult, InstancePath, Validators,
format_validators, CompilationResult, Validators,
},
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -9,7 +9,8 @@
use crate::{
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{format_validators, CompilationResult, InstancePath, Validators},
keywords::{format_validators, CompilationResult, Validators},
paths::InstancePath,
validator::Validate,
};
use ahash::AHashMap;

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{CompilationError, ErrorIterator},
keywords::{format_vec_of_validators, CompilationResult, InstancePath, Validators},
keywords::{format_vec_of_validators, CompilationResult, Validators},
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{format_vec_of_validators, CompilationResult, InstancePath, Validators},
keywords::{format_vec_of_validators, CompilationResult, Validators},
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,4 +1,4 @@
use crate::keywords::InstancePath;
use crate::paths::InstancePath;
use crate::{
compilation::JSONSchema,

View File

@ -7,7 +7,7 @@ use crate::{
use serde_json::{Map, Number, Value};
use std::f64::EPSILON;
use super::InstancePath;
use crate::paths::InstancePath;
struct ConstArrayValidator {
value: Vec<Value>,

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{error, no_error, ErrorIterator, ValidationError},
keywords::{format_validators, CompilationResult, InstancePath, Validators},
keywords::{format_validators, CompilationResult, Validators},
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -4,7 +4,8 @@ use crate::{
content_encoding::{ContentEncodingCheckType, ContentEncodingConverterType},
content_media_type::ContentMediaTypeCheckType,
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -2,9 +2,9 @@ use crate::{
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator},
keywords::{
format_key_value_validators, required::RequiredValidator, CompilationResult, InstancePath,
Validators,
format_key_value_validators, required::RequiredValidator, CompilationResult, Validators,
},
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{helpers, CompilationResult, InstancePath},
keywords::{helpers, CompilationResult},
paths::InstancePath,
primitive_type::{PrimitiveType, PrimitiveTypesBitMap},
validator::Validate,
};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use num_cmp::NumCmp;

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use num_cmp::NumCmp;

View File

@ -2,7 +2,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
Draft,
};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{no_error, ErrorIterator},
keywords::{format_validators, CompilationResult, InstancePath, Validators},
keywords::{format_validators, CompilationResult, Validators},
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -3,8 +3,9 @@ use crate::{
error::{no_error, ErrorIterator},
keywords::{
boolean::TrueValidator, format_validators, format_vec_of_validators, CompilationResult,
InstancePath, Validators,
Validators,
},
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{type_, CompilationResult, InstancePath},
keywords::{type_, CompilationResult},
paths::InstancePath,
primitive_type::{PrimitiveType, PrimitiveTypesBitMap},
validator::Validate,
};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use num_cmp::NumCmp;

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use num_cmp::NumCmp;

View File

@ -35,71 +35,10 @@ pub(crate) mod required;
pub(crate) mod type_;
pub(crate) mod unique_items;
use crate::{error, validator::Validate};
use std::{cell::RefCell, ops::Deref};
pub(crate) type CompilationResult = Result<BoxedValidator, error::CompilationError>;
pub(crate) type BoxedValidator = Box<dyn Validate + Send + Sync>;
pub(crate) type Validators = Vec<BoxedValidator>;
pub(crate) type InstancePathInner = RefCell<Vec<PathChunk>>;
#[derive(Clone, Debug)]
pub(crate) enum PathChunk {
Name(String),
Index(usize),
}
#[derive(Clone, Debug)]
pub(crate) struct InstancePath(InstancePathInner);
impl InstancePath {
pub(crate) fn new(inner: InstancePathInner) -> Self {
Self(inner)
}
#[inline]
pub(crate) fn push(&self, value: impl Into<PathChunk>) {
self.borrow_mut().push(value.into())
}
#[inline]
pub(crate) fn pop(&self) {
self.borrow_mut().pop();
}
}
impl From<String> for PathChunk {
#[inline]
fn from(value: String) -> Self {
PathChunk::Name(value)
}
}
impl From<usize> for PathChunk {
#[inline]
fn from(value: usize) -> Self {
PathChunk::Index(value)
}
}
impl Deref for InstancePath {
type Target = InstancePathInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<&InstancePath> for Vec<String> {
#[inline]
fn from(path: &InstancePath) -> Self {
path.0
.borrow()
.iter()
.map(|item| match item {
PathChunk::Name(value) => value.to_string(),
PathChunk::Index(idx) => idx.to_string(),
})
.collect()
}
}
fn format_validators(validators: &[BoxedValidator]) -> String {
match validators.len() {

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{error, no_error, ErrorIterator, ValidationError},
keywords::{format_validators, CompilationResult, InstancePath, Validators},
keywords::{format_validators, CompilationResult, Validators},
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{format_vec_of_validators, CompilationResult, InstancePath, Validators},
keywords::{format_vec_of_validators, CompilationResult, Validators},
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use regex::{Captures, Regex};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator},
keywords::{format_validators, CompilationResult, InstancePath, Validators},
keywords::{format_validators, CompilationResult, Validators},
paths::InstancePath,
validator::Validate,
};
use regex::Regex;

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator},
keywords::{format_key_value_validators, CompilationResult, InstancePath, Validators},
keywords::{format_key_value_validators, CompilationResult, Validators},
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{error, no_error, ErrorIterator, ValidationError},
keywords::{format_validators, CompilationResult, InstancePath, Validators},
keywords::{format_validators, CompilationResult, Validators},
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -9,7 +9,7 @@ use serde_json::Value;
use std::borrow::Cow;
use url::Url;
use super::InstancePath;
use crate::paths::InstancePath;
pub(crate) struct RefValidator {
reference: Url,

View File

@ -1,7 +1,8 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
paths::InstancePath,
validator::Validate,
};
use serde_json::{Map, Value};

View File

@ -8,7 +8,7 @@ use crate::{
use serde_json::{Map, Number, Value};
use std::convert::TryFrom;
use super::InstancePath;
use crate::paths::InstancePath;
pub(crate) struct MultipleTypesValidator {
types: PrimitiveTypesBitMap,

View File

@ -1,12 +1,13 @@
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, ErrorIterator, ValidationError},
keywords::{CompilationResult, InstancePath},
keywords::CompilationResult,
validator::Validate,
};
use ahash::{AHashSet, AHasher};
use serde_json::{Map, Value};
use crate::paths::InstancePath;
use std::hash::{Hash, Hasher};
// Based on implementation proposed by Sven Marnach:

View File

@ -47,12 +47,14 @@
//! if let Err(errors) = result {
//! for error in errors {
//! println!("Validation error: {}", error);
//! println!("Instance path: {:?}", error.instance_path);
//! println!("Instance path: {}", error.instance_path);
//! }
//! }
//! Ok(())
//! }
//! ```
//! Each error has an `instance_path` attribute that indicates the path to the erroneous part within the validated instance.
//! It could be transformed to JSON Pointer via `.to_string()` or to `Vec<String>` via `.into_vec()`.
#![warn(
clippy::cast_possible_truncation,
clippy::doc_markdown,
@ -81,10 +83,12 @@ mod content_encoding;
mod content_media_type;
pub mod error;
mod keywords;
pub mod paths;
pub mod primitive_type;
mod resolver;
mod schemas;
mod validator;
pub use compilation::{options::CompilationOptions, JSONSchema};
pub use error::{CompilationError, ErrorIterator, ValidationError};
pub use schemas::Draft;

111
jsonschema/src/paths.rs Normal file
View File

@ -0,0 +1,111 @@
//! Facilities for working with paths within schemas or validated instances.
use std::fmt::Write;
use std::{cell::RefCell, fmt, ops::Deref};
#[derive(Clone, Debug, Eq, PartialEq)]
/// JSON Pointer as a wrapper around individual path components
pub struct JSONPointer(Vec<PathChunk>);
impl JSONPointer {
/// JSON pointer as a vector of strings. Each component is casted to `String`.
pub fn into_vec(self) -> Vec<String> {
self.0
.iter()
.map(|item| match item {
PathChunk::Name(value) => value.to_string(),
PathChunk::Index(idx) => idx.to_string(),
})
.collect()
}
}
impl Default for JSONPointer {
fn default() -> Self {
JSONPointer(Vec::new())
}
}
impl fmt::Display for JSONPointer {
fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.0.is_empty() {
for chunk in &self.0 {
f.write_char('/')?;
match chunk {
PathChunk::Name(value) => f.write_str(value)?,
PathChunk::Index(idx) => itoa::fmt(&mut f, *idx)?,
}
}
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum PathChunk {
Name(String),
Index(usize),
}
pub(crate) type InstancePathInner = RefCell<Vec<PathChunk>>;
#[derive(Clone, Debug)]
pub(crate) struct InstancePath(InstancePathInner);
impl InstancePath {
pub(crate) fn new(inner: InstancePathInner) -> Self {
Self(inner)
}
#[inline]
pub(crate) fn push(&self, value: impl Into<PathChunk>) {
self.borrow_mut().push(value.into())
}
#[inline]
pub(crate) fn pop(&self) {
self.borrow_mut().pop();
}
}
impl From<String> for PathChunk {
#[inline]
fn from(value: String) -> Self {
PathChunk::Name(value)
}
}
impl From<usize> for PathChunk {
#[inline]
fn from(value: usize) -> Self {
PathChunk::Index(value)
}
}
impl Deref for InstancePath {
type Target = InstancePathInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<&InstancePath> for JSONPointer {
#[inline]
fn from(path: &InstancePath) -> Self {
JSONPointer(path.0.borrow().iter().map(|item| item.to_owned()).collect())
}
}
impl From<&[&str]> for JSONPointer {
#[inline]
fn from(path: &[&str]) -> Self {
JSONPointer(
path.iter()
.map(|item| PathChunk::Name(item.to_string()))
.collect(),
)
}
}
impl From<&[PathChunk]> for JSONPointer {
#[inline]
fn from(path: &[PathChunk]) -> Self {
JSONPointer(path.to_vec())
}
}

View File

@ -1,4 +1,4 @@
use crate::{compilation::JSONSchema, error::ErrorIterator, keywords::InstancePath};
use crate::{compilation::JSONSchema, error::ErrorIterator, paths::InstancePath};
use serde_json::Value;
use std::fmt;

View File

@ -44,11 +44,7 @@ fn test_draft(_server_address: &str, test_case: TestCase) {
);
let errors: Vec<_> = result.expect_err("Errors").collect();
for error in errors {
let pointer = if error.instance_path.is_empty() {
"".to_string()
} else {
format!("/{}", error.instance_path.join("/"))
};
let pointer = error.instance_path.to_string();
assert_eq!(test_case.instance.pointer(&pointer), Some(&*error.instance))
}
}
@ -92,9 +88,12 @@ fn test_instance_path() {
.next()
.expect("Validation error");
assert_eq!(
error.instance_path, instance_path,
error.instance_path.into_vec(),
instance_path,
"File: {}; Suite ID: {}; Test ID: {}",
filename, suite_id, test_id
filename,
suite_id,
test_id
)
}
}