feat: Define CompilationOptions to allow for easier expansion (ie. contentType, format, mediaType)

This commit is contained in:
Samuele Maci 2020-07-03 20:56:31 +01:00 committed by Dmitry Dygalo
parent bc04d70ae3
commit bfd508a98b
48 changed files with 337 additions and 190 deletions

View File

@ -5,6 +5,7 @@
### Added
- `ToString` trait implementation for validators.
- Define `JSONSchema::options` to customise `JSONSchema` compilation [#131](https://github.com/Stranger6667/jsonschema-rs/issues/131)
### Fixed

View File

@ -1,5 +1,5 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use jsonschema::*;
use jsonschema::JSONSchema;
use jsonschema_valid::schemas;
use serde_json::{from_str, json, Value};
use std::{fs::File, io::Read, path::Path};
@ -32,9 +32,9 @@ macro_rules! bench {
#[allow(dead_code)]
fn [<bench_ $name>](c: &mut Criterion) {
let schema = json!($schema);
let validator = JSONSchema::compile(&schema, None).unwrap();
let validator = JSONSchema::compile(&schema).unwrap();
let suffix = strip_characters(stringify!($schema));
c.bench_function(format!("jsonschema-rs {} compile {}", $name, suffix).as_str(), |b| b.iter(|| JSONSchema::compile(&schema, None).unwrap()));
c.bench_function(format!("jsonschema-rs {} compile {}", $name, suffix).as_str(), |b| b.iter(|| JSONSchema::compile(&schema).unwrap()));
$(
let instance = black_box(json!($valid));
assert!(validator.is_valid(&instance));
@ -62,9 +62,9 @@ macro_rules! bench {
paste::item! {
fn [<bench_ $name>](c: &mut Criterion) {
let schema = json!($schema);
let validator = JSONSchema::compile(&schema, None).unwrap();
let validator = JSONSchema::compile(&schema).unwrap();
let suffix = strip_characters(stringify!($schema));
c.bench_function(format!("jsonschema-rs {} compile {}", $name, suffix).as_str(), |b| b.iter(|| JSONSchema::compile(&schema, None).unwrap()));
c.bench_function(format!("jsonschema-rs {} compile {}", $name, suffix).as_str(), |b| b.iter(|| JSONSchema::compile(&schema).unwrap()));
$(
let instance = black_box(json!($invalid));
assert!(!validator.is_valid(&instance));
@ -85,9 +85,9 @@ fn big_schema(c: &mut Criterion) {
let instance = black_box(read_json("benches/canada.json"));
// jsonschema
let validator = JSONSchema::compile(&schema, None).unwrap();
let validator = JSONSchema::compile(&schema).unwrap();
c.bench_function("compare jsonschema-rs big schema compile", |b| {
b.iter(|| JSONSchema::compile(&schema, None).unwrap())
b.iter(|| JSONSchema::compile(&schema).unwrap())
});
c.bench_function("compare jsonschema-rs big schema is_valid", |b| {
b.iter(|| validator.is_valid(&instance))
@ -130,9 +130,9 @@ fn small_schema(c: &mut Criterion) {
black_box(json!([10, "world", [1, "a", true], {"a": "a", "b": "b", "c": "xy"}, "str", 5]));
// jsonschema
let validator = JSONSchema::compile(&schema, None).unwrap();
let validator = JSONSchema::compile(&schema).unwrap();
c.bench_function("compare jsonschema-rs small schema compile", |b| {
b.iter(|| JSONSchema::compile(&schema, None).unwrap())
b.iter(|| JSONSchema::compile(&schema).unwrap())
});
c.bench_function("compare jsonschema-rs small schema is_valid valid", |b| {
b.iter(|| validator.is_valid(&valid))

View File

@ -15,7 +15,7 @@ fn main() -> Result<(), Error> {
eprintln!("Instance {}", instance);
eprintln!("Number of Iterations {}", number_of_iterations);
let compiled = JSONSchema::compile(&schema, None).unwrap();
let compiled = JSONSchema::compile(&schema).unwrap();
for _ in 0..number_of_iterations {
compiled.is_valid(&instance);
}

View File

@ -44,19 +44,15 @@ impl From<JSONSchemaError> for PyErr {
}
}
fn get_draft(draft: Option<u8>) -> PyResult<Draft> {
if let Some(value) = draft {
match value {
DRAFT4 => Ok(jsonschema::Draft::Draft4),
DRAFT6 => Ok(jsonschema::Draft::Draft6),
DRAFT7 => Ok(jsonschema::Draft::Draft7),
_ => Err(exceptions::ValueError::py_err(format!(
"Unknown draft: {}",
value
))),
}
} else {
Ok(jsonschema::Draft::default())
fn get_draft(draft: u8) -> PyResult<Draft> {
match draft {
DRAFT4 => Ok(jsonschema::Draft::Draft4),
DRAFT6 => Ok(jsonschema::Draft::Draft6),
DRAFT7 => Ok(jsonschema::Draft::Draft7),
_ => Err(exceptions::ValueError::py_err(format!(
"Unknown draft: {}",
draft
))),
}
}
@ -72,11 +68,15 @@ fn get_draft(draft: Option<u8>) -> PyResult<Draft> {
#[pyfunction]
#[text_signature = "(schema, instance, draft=None)"]
fn is_valid(schema: &PyAny, instance: &PyAny, draft: Option<u8>) -> PyResult<bool> {
let draft = get_draft(draft).map(Some)?;
let schema = ser::to_value(schema)?;
let instance = ser::to_value(instance)?;
let compiled =
jsonschema::JSONSchema::compile(&schema, draft).map_err(JSONSchemaError::Compilation)?;
let mut options = jsonschema::JSONSchema::options();
if let Some(raw_draft_version) = draft {
options.with_draft(get_draft(raw_draft_version)?);
}
let compiled = options
.compile(&schema)
.map_err(JSONSchemaError::Compilation)?;
Ok(compiled.is_valid(&instance))
}
@ -100,13 +100,17 @@ struct JSONSchema {
impl JSONSchema {
#[new]
fn new(schema: &PyAny, draft: Option<u8>) -> PyResult<Self> {
let draft = get_draft(draft).map(Some)?;
let raw_schema = ser::to_value(schema)?;
let mut options = jsonschema::JSONSchema::options();
if let Some(raw_draft_version) = draft {
options.with_draft(get_draft(raw_draft_version)?);
}
// Currently, it is the simplest way to pass a reference to `JSONSchema`
// It is cleaned up in the `Drop` implementation
let schema: &'static Value = Box::leak(Box::new(raw_schema));
Ok(JSONSchema {
schema: jsonschema::JSONSchema::compile(schema, draft)
schema: options
.compile(schema)
.map_err(JSONSchemaError::Compilation)?,
raw_schema: schema,
})

View File

@ -0,0 +1,52 @@
use super::options::CompilationOptions;
use crate::schemas;
use serde_json::Value;
use std::borrow::Cow;
use url::{ParseError, Url};
/// Context holds information about used draft and current scope.
#[derive(Debug)]
pub(crate) struct CompilationContext<'a> {
pub(crate) scope: Cow<'a, Url>,
pub(crate) config: Cow<'a, CompilationOptions>,
}
impl<'a> CompilationContext<'a> {
pub(crate) fn new(scope: Url, config: Cow<'a, CompilationOptions>) -> Self {
CompilationContext {
scope: Cow::Owned(scope),
config,
}
}
#[allow(clippy::doc_markdown)]
/// Push a new scope. All URLs built from the new context will have this scope in them.
/// Before push:
/// scope = http://example.com/
/// build_url("#/definitions/foo") -> "http://example.com/#/definitions/foo"
/// After push this schema - {"$id": "folder/", ...}
/// scope = http://example.com/folder/
/// build_url("#/definitions/foo") -> "http://example.com/folder/#/definitions/foo"
///
/// In other words it keeps track of sub-folders during compilation.
#[inline]
pub(crate) fn push(&'a self, schema: &Value) -> Result<Self, ParseError> {
if let Some(id) = schemas::id_of(self.config.draft(), schema) {
let scope = Url::options().base_url(Some(&self.scope)).parse(id)?;
Ok(CompilationContext {
scope: Cow::Owned(scope),
config: Cow::Borrowed(&self.config),
})
} else {
Ok(CompilationContext {
scope: Cow::Borrowed(self.scope.as_ref()),
config: Cow::Borrowed(&self.config),
})
}
}
/// Build a new URL. Used for `ref` compilation to keep their full paths.
pub(crate) fn build_url(&self, reference: &str) -> Result<Url, ParseError> {
Url::options().base_url(Some(&self.scope)).parse(reference)
}
}

View File

@ -1,26 +1,30 @@
//! Schema compilation.
//! The main idea is to compile the input JSON Schema to a validators tree that will contain
//! everything needed to perform such validation in runtime.
pub(crate) mod context;
pub(crate) mod options;
use crate::{
error::{CompilationError, ErrorIterator},
keywords,
keywords::Validators,
resolver::Resolver,
schemas,
};
use context::CompilationContext;
use options::CompilationOptions;
use serde_json::Value;
use std::borrow::Cow;
use url::{ParseError, Url};
use url::Url;
pub(crate) const DEFAULT_ROOT_URL: &str = "json-schema:///";
/// The structure that holds a JSON Schema compiled into a validation tree
#[derive(Debug)]
pub struct JSONSchema<'a> {
pub(crate) draft: schemas::Draft,
pub(crate) schema: &'a Value,
pub(crate) validators: Validators,
pub(crate) resolver: Resolver<'a>,
pub(crate) context: CompilationContext<'a>,
}
lazy_static::lazy_static! {
@ -28,34 +32,29 @@ lazy_static::lazy_static! {
}
impl<'a> JSONSchema<'a> {
/// Compile the input schema into a validation tree
pub fn compile(
schema: &'a Value,
draft: Option<schemas::Draft>,
) -> Result<JSONSchema<'a>, CompilationError> {
// Draft is detected in the following precedence order:
// - Explicitly specified;
// - $schema field in the document;
// - Draft7;
let draft = draft.unwrap_or_else(|| {
schemas::draft_from_schema(schema).unwrap_or(schemas::Draft::Draft7)
});
let scope = match schemas::id_of(draft, schema) {
Some(url) => url::Url::parse(url)?,
None => DEFAULT_SCOPE.clone(),
};
let resolver = Resolver::new(draft, &scope, schema)?;
let context = CompilationContext::new(scope, draft);
/// Return a default `CompilationOptions` that can configure
/// `JSONSchema` compilaton flow.
///
/// Using options you will be able to configure the draft version
/// to use during `JSONSchema` compilation
///
/// Example of usage:
/// ```rust
/// # use crate::jsonschema::{Draft, JSONSchema};
/// # let schema = serde_json::json!({});
/// let maybe_jsonschema: Result<JSONSchema, _> = JSONSchema::options()
/// .with_draft(Draft::Draft7)
/// .compile(&schema);
/// ```
pub fn options() -> CompilationOptions {
CompilationOptions::default()
}
let mut validators = compile_validators(schema, &context)?;
validators.shrink_to_fit();
Ok(JSONSchema {
draft,
schema,
resolver,
validators,
})
/// Compile the input schema into a validation tree.
///
/// The method is equivalent to `JSONSchema::options().compile(schema)`
pub fn compile(schema: &'a Value) -> Result<JSONSchema<'a>, CompilationError> {
Self::options().compile(schema)
}
/// Run validation against `instance` and return an iterator over `ValidationError` in the error case.
@ -85,53 +84,6 @@ impl<'a> JSONSchema<'a> {
}
}
/// Context holds information about used draft and current scope.
#[derive(Debug)]
pub(crate) struct CompilationContext<'a> {
pub(crate) scope: Cow<'a, Url>,
pub(crate) draft: schemas::Draft,
}
impl<'a> CompilationContext<'a> {
pub(crate) fn new(scope: Url, draft: schemas::Draft) -> Self {
CompilationContext {
scope: Cow::Owned(scope),
draft,
}
}
#[allow(clippy::doc_markdown)]
/// Push a new scope. All URLs built from the new context will have this scope in them.
/// Before push:
/// scope = http://example.com/
/// build_url("#/definitions/foo") -> "http://example.com/#/definitions/foo"
/// After push this schema - {"$id": "folder/", ...}
/// scope = http://example.com/folder/
/// build_url("#/definitions/foo") -> "http://example.com/folder/#/definitions/foo"
///
/// In other words it keeps track of sub-folders during compilation.
#[inline]
pub(crate) fn push(&'a self, schema: &Value) -> Result<Self, ParseError> {
if let Some(id) = schemas::id_of(self.draft, schema) {
let scope = Url::options().base_url(Some(&self.scope)).parse(id)?;
Ok(CompilationContext {
scope: Cow::Owned(scope),
draft: self.draft,
})
} else {
Ok(CompilationContext {
scope: Cow::Borrowed(self.scope.as_ref()),
draft: self.draft,
})
}
}
/// Build a new URL. Used for `ref` compilation to keep their full paths.
pub(crate) fn build_url(&self, reference: &str) -> Result<Url, ParseError> {
Url::options().base_url(Some(&self.scope)).parse(reference)
}
}
/// Compile JSON schema into a tree of validators.
#[inline]
pub(crate) fn compile_validators(
@ -154,7 +106,7 @@ pub(crate) fn compile_validators(
} else {
let mut validators = Vec::with_capacity(object.len());
for (keyword, subschema) in object {
if let Some(compilation_func) = context.draft.get_validator(keyword) {
if let Some(compilation_func) = context.config.draft().get_validator(keyword) {
if let Some(validator) = compilation_func(object, subschema, &context) {
validators.push(validator?)
}
@ -169,9 +121,9 @@ pub(crate) fn compile_validators(
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ValidationError;
use serde_json::*;
use super::JSONSchema;
use crate::{error::ValidationError, schemas};
use serde_json::{from_str, json, Value};
use std::{borrow::Cow, fs::File, io::Read, path::Path};
use url::Url;
@ -189,7 +141,7 @@ mod tests {
fn only_keyword() {
// When only one keyword is specified
let schema = json!({"type": "string"});
let compiled = JSONSchema::compile(&schema, None).unwrap();
let compiled = JSONSchema::compile(&schema).unwrap();
let value1 = json!("AB");
let value2 = json!(1);
// And only this validator
@ -201,7 +153,7 @@ mod tests {
#[test]
fn resolve_ref() {
let schema = load("tests/suite/tests/draft7/ref.json", 4);
let compiled = JSONSchema::compile(&schema, None).unwrap();
let compiled = JSONSchema::compile(&schema).unwrap();
let url = Url::parse("json-schema:///#/definitions/a").unwrap();
if let (resource, Cow::Borrowed(resolved)) = compiled
.resolver
@ -217,7 +169,7 @@ mod tests {
fn validate_ref() {
let schema = load("tests/suite/tests/draft7/ref.json", 1);
let value = json!({"bar": 3});
let compiled = JSONSchema::compile(&schema, None).unwrap();
let compiled = JSONSchema::compile(&schema).unwrap();
assert!(compiled.validate(&value).is_ok());
let value = json!({"bar": true});
assert!(compiled.validate(&value).is_err());
@ -226,7 +178,7 @@ mod tests {
#[test]
fn wrong_schema_type() {
let schema = json!([1]);
let compiled = JSONSchema::compile(&schema, None);
let compiled = JSONSchema::compile(&schema);
assert!(compiled.is_err());
}
@ -234,7 +186,7 @@ mod tests {
fn multiple_errors() {
let schema = json!({"minProperties": 2, "propertyNames": {"minLength": 3}});
let value = json!({"a": 3});
let compiled = JSONSchema::compile(&schema, None).unwrap();
let compiled = JSONSchema::compile(&schema).unwrap();
let result = compiled.validate(&value);
let errors: Vec<ValidationError> = result.unwrap_err().collect();
assert_eq!(errors.len(), 2);

107
src/compilation/options.rs Normal file
View File

@ -0,0 +1,107 @@
use crate::{
compilation::{compile_validators, context::CompilationContext, JSONSchema, DEFAULT_SCOPE},
error::CompilationError,
resolver::Resolver,
schemas,
};
use serde_json::Value;
use std::{borrow::Cow, fmt};
/// Full configuration to guide the `JSONSchema` compilation.
///
/// Using a `CompilationOptions` instance you can configure the supported draft,
/// content media types and more (check the exposed methods)
#[derive(Clone, Default)]
pub struct CompilationOptions {
draft: Option<schemas::Draft>,
}
impl CompilationOptions {
pub(crate) fn draft(&self) -> schemas::Draft {
self.draft.unwrap_or_default()
}
/// Compile `schema` into `JSONSchema` using the currently defined options.
pub fn compile<'a>(&self, schema: &'a Value) -> Result<JSONSchema<'a>, CompilationError> {
// Draft is detected in the following precedence order:
// - Explicitly specified;
// - $schema field in the document;
// - Draft::default()
// Clone needed because we are going to store a Copy-on-Write (Cow) instance
// into the final JSONSchema as well as passing `self` (the instance and not
// the reference) would require Copy trait implementation from
// `CompilationOptions` which is something that we would like to avoid as
// options might contain heap-related objects (ie. an HashMap) and we want the
// memory-related operations to be explicit
let mut config = self.clone();
if self.draft.is_none() {
if let Some(draft) = schemas::draft_from_schema(schema) {
config.with_draft(draft);
}
}
let processed_config: Cow<'_, CompilationOptions> = Cow::Owned(config);
let draft = processed_config.draft();
let scope = match schemas::id_of(draft, schema) {
Some(url) => url::Url::parse(url)?,
None => DEFAULT_SCOPE.clone(),
};
let resolver = Resolver::new(draft, &scope, schema)?;
let context = CompilationContext::new(scope, processed_config);
let mut validators = compile_validators(schema, &context)?;
validators.shrink_to_fit();
Ok(JSONSchema {
schema,
resolver,
validators,
context,
})
}
/// Ensure that the schema is going to be compiled using the defined Draft.
///
/// ```rust
/// # use jsonschema::{Draft, CompilationOptions};
/// # let mut options = CompilationOptions::default();
/// options.with_draft(Draft::Draft4);
/// ```
#[inline]
pub fn with_draft(&mut self, draft: schemas::Draft) -> &mut Self {
self.draft = Some(draft);
self
}
}
impl fmt::Debug for CompilationOptions {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("CompilationConfig")
.field("draft", &self.draft)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::CompilationOptions;
use crate::schemas::Draft;
use serde_json::{json, Value};
use test_case::test_case;
#[test_case(Some(Draft::Draft4), &json!({}) => Draft::Draft4)]
#[test_case(None, &json!({"$schema": "http://json-schema.org/draft-06/schema#"}) => Draft::Draft6)]
#[test_case(None, &json!({}) => Draft::default())]
fn test_ensure_that_draft_detection_is_honored(
draft_version_in_options: Option<Draft>,
schema: &Value,
) -> Draft {
let mut options = CompilationOptions::default();
if let Some(draft_version) = draft_version_in_options {
options.with_draft(draft_version);
}
let compiled = options.compile(schema).unwrap();
compiled.context.config.draft()
}
}

View File

@ -60,7 +60,7 @@ pub struct ValidationError<'a> {
///
/// let schema = json!({"maxLength": 5});
/// let instance = json!("foo");
/// if let Ok(compiled) = JSONSchema::compile(&schema, None) {
/// if let Ok(compiled) = JSONSchema::compile(&schema) {
/// let result = compiled.validate(&instance);
/// if let Err(errors) = result {
/// for error in errors {

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{
boolean::{FalseValidator, TrueValidator},

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{format_validators, CompilationResult, Validators},
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{CompilationError, ErrorIterator},
keywords::{format_vec_of_validators, CompilationResult, Validators},
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{CompilationError, ValidationError},
keywords::{format_vec_of_validators, CompilationResult, Validators},
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{error, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{no_error, ErrorIterator, ValidationError},
keywords::{format_validators, CompilationResult, Validators},
validator::Validate,

View File

@ -1,6 +1,6 @@
//! Validators for `contentMediaType` and `contentEncoding` keywords.
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator},
keywords::{
format_key_value_validators, required::RequiredValidator, CompilationResult, Validators,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{CompilationError, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,6 +1,6 @@
//! Validator for `format` keyword.
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,
@ -163,33 +163,34 @@ pub(crate) fn compile(
context: &CompilationContext,
) -> Option<CompilationResult> {
if let Value::String(format) = schema {
let draft_version = context.config.draft();
match format.as_str() {
"date-time" => Some(DateTimeValidator::compile()),
"date" => Some(DateValidator::compile()),
"email" => Some(EmailValidator::compile()),
"hostname" => Some(HostnameValidator::compile()),
"idn-email" => Some(IDNEmailValidator::compile()),
"idn-hostname" if context.draft == Draft::Draft7 => {
"idn-hostname" if draft_version == Draft::Draft7 => {
Some(IDNHostnameValidator::compile())
}
"ipv4" => Some(IpV4Validator::compile()),
"ipv6" => Some(IpV6Validator::compile()),
"iri-reference" if context.draft == Draft::Draft7 => {
"iri-reference" if draft_version == Draft::Draft7 => {
Some(IRIReferenceValidator::compile())
}
"iri" if context.draft == Draft::Draft7 => Some(IRIValidator::compile()),
"json-pointer" if context.draft == Draft::Draft6 || context.draft == Draft::Draft7 => {
"iri" if draft_version == Draft::Draft7 => Some(IRIValidator::compile()),
"json-pointer" if draft_version == Draft::Draft6 || draft_version == Draft::Draft7 => {
Some(JSONPointerValidator::compile())
}
"regex" => Some(RegexValidator::compile()),
"relative-json-pointer" if context.draft == Draft::Draft7 => {
"relative-json-pointer" if draft_version == Draft::Draft7 => {
Some(RelativeJSONPointerValidator::compile())
}
"time" => Some(TimeValidator::compile()),
"uri-reference" if context.draft == Draft::Draft6 || context.draft == Draft::Draft7 => {
"uri-reference" if draft_version == Draft::Draft6 || draft_version == Draft::Draft7 => {
Some(URIReferenceValidator::compile())
}
"uri-template" if context.draft == Draft::Draft6 || context.draft == Draft::Draft7 => {
"uri-template" if draft_version == Draft::Draft6 || draft_version == Draft::Draft7 => {
Some(URITemplateValidator::compile())
}
"uri" => Some(URIValidator::compile()),
@ -209,7 +210,7 @@ mod tests {
fn ignored_format() {
let schema = json!({"format": "custom", "type": "string"});
let instance = json!("foo");
let compiled = JSONSchema::compile(&schema, None).unwrap();
let compiled = JSONSchema::compile(&schema).unwrap();
assert!(compiled.is_valid(&instance))
}
}

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{no_error, ErrorIterator},
keywords::{format_validators, CompilationResult, Validators},
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{no_error, ErrorIterator},
keywords::{
boolean::TrueValidator, format_validators, format_vec_of_validators, CompilationResult,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::CompilationContext,
compilation::context::CompilationContext,
keywords::{exclusive_maximum, maximum, CompilationResult},
};
use serde_json::{Map, Value};

View File

@ -1,5 +1,5 @@
use crate::{
compilation::CompilationContext,
compilation::context::CompilationContext,
keywords::{exclusive_minimum, minimum, CompilationResult},
};
use serde_json::{Map, Value};

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{type_, CompilationResult},
primitive_type::{PrimitiveType, PrimitiveTypesBitMap},

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -161,7 +161,7 @@ mod tests {
#[test_case(json!({"type": ["integer", "null"], "$schema": "http://json-schema.org/draft-04/schema#"}), "type: [integer, null]")]
#[test_case(json!({"uniqueItems": true}), "uniqueItems: true")]
fn debug_representation(schema: Value, expected: &str) {
let compiled = JSONSchema::compile(&schema, None).unwrap();
let compiled = JSONSchema::compile(&schema).unwrap();
assert_eq!(format!("{:?}", compiled.validators[0]), expected);
}
@ -194,7 +194,7 @@ mod tests {
#[test_case(json!({"type": ["integer", "string"]}), json!(null), r#"'null' is not of types 'integer', 'string'"#)]
#[test_case(json!({"uniqueItems": true}), json!([1, 1]), r#"'[1,1]' has non-unique elements"#)]
fn error_message(schema: Value, instance: Value, expected: &str) {
let compiled = JSONSchema::compile(&schema, None).unwrap();
let compiled = JSONSchema::compile(&schema).unwrap();
let errors: Vec<_> = compiled
.validate(&instance)
.expect_err(&format!(
@ -236,7 +236,7 @@ mod tests {
#[test_case(json!({"propertyNames": {"maxLength": 3}}))]
fn is_valid_another_type(schema: Value) {
let instance = json!(null);
let compiled = JSONSchema::compile(&schema, None).unwrap();
let compiled = JSONSchema::compile(&schema).unwrap();
assert!(compiled.is_valid(&instance))
}
@ -244,7 +244,7 @@ mod tests {
#[test_case(json!({"additionalItems": false, "items": true}), json!([]))]
fn is_valid(schema: Value, instance: Value) {
let data = json!(instance);
let compiled = JSONSchema::compile(&schema, None).unwrap();
let compiled = JSONSchema::compile(&schema).unwrap();
assert!(compiled.is_valid(&data))
}
}

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::ValidationError,
keywords::{format_validators, CompilationResult, Validators},
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::{format_vec_of_validators, CompilationResult, Validators},
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator},
keywords::{format_validators, CompilationResult, Validators},
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{no_error, CompilationError, ErrorIterator},
keywords::{format_key_value_validators, CompilationResult, Validators},
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{no_error, ErrorIterator, ValidationError},
keywords::{format_validators, CompilationResult, Validators},
validator::Validate,

View File

@ -1,11 +1,12 @@
use crate::{
compilation::{compile_validators, CompilationContext, JSONSchema},
compilation::{compile_validators, context::CompilationContext, JSONSchema},
error::{error, ErrorIterator, ValidationError},
keywords::{CompilationResult, Validators},
validator::Validate,
};
use parking_lot::RwLock;
use serde_json::{Map, Value};
use std::borrow::Cow;
use url::Url;
pub(crate) struct RefValidator {
@ -32,11 +33,12 @@ impl RefValidator {
#[inline]
fn ensure_validators<'a>(&self, schema: &'a JSONSchema) -> Result<(), ValidationError<'a>> {
if self.validators.read().is_none() {
let (scope, resolved) =
schema
.resolver
.resolve_fragment(schema.draft, &self.reference, schema.schema)?;
let context = CompilationContext::new(scope, schema.draft);
let (scope, resolved) = schema.resolver.resolve_fragment(
schema.context.config.draft(),
&self.reference,
schema.schema,
)?;
let context = CompilationContext::new(scope, Cow::Borrowed(&schema.context.config));
let validators = compile_validators(&resolved, &context)?;
// Inject the validators into self.validators

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, CompilationError, ErrorIterator, ValidationError},
keywords::CompilationResult,
primitive_type::{PrimitiveType, PrimitiveTypesBitMap},

View File

@ -1,5 +1,5 @@
use crate::{
compilation::{CompilationContext, JSONSchema},
compilation::{context::CompilationContext, JSONSchema},
error::{no_error, ErrorIterator, ValidationError},
keywords::CompilationResult,
validator::Validate,

View File

@ -7,25 +7,50 @@
//! - JSON Schema drafts 6, 7 (all test cases);
//! - Loading remote documents via HTTP(S);
//!
//! ## Example:
//!
//! ## Usage Examples:
//! A schema can be compiled with two main flavours:
//! * using default configurations
//! ```rust
//! use jsonschema::{JSONSchema, Draft, CompilationError};
//! # use jsonschema::{CompilationError, Draft, JSONSchema};
//! # use serde_json::json;
//! # fn foo() -> Result<(), CompilationError> {
//! # let schema = json!({"maxLength": 5});
//! let compiled_schema = JSONSchema::compile(&schema)?;
//! # Ok(())
//! # }
//! ```
//! * using custom configurations (such as define a Draft version)
//! ```rust
//! # use jsonschema::{CompilationError, Draft, JSONSchema};
//! # use serde_json::json;
//! # fn foo() -> Result<(), CompilationError> {
//! # let schema = json!({"maxLength": 5});
//! let compiled_schema = JSONSchema::options()
//! .with_draft(Draft::Draft7)
//! .compile(&schema)?;
//! # Ok(())
//! # }
//! ```
//!
//! ## Example (CLI tool to highlight print errors)
//! ```rust
//! use jsonschema::{CompilationError, Draft, JSONSchema};
//! use serde_json::json;
//!
//!fn main() -> Result<(), CompilationError> {
//! let schema = json!({"maxLength": 5});
//! let instance = json!("foo");
//! let compiled = JSONSchema::compile(&schema, Some(Draft::Draft7))?;
//! let result = compiled.validate(&instance);
//! if let Err(errors) = result {
//! for error in errors {
//! println!("Validation error: {}", error)
//! }
//! }
//! Ok(())
//! fn main() -> Result<(), CompilationError> {
//! let schema = json!({"maxLength": 5});
//! let instance = json!("foo");
//! let compiled = JSONSchema::options()
//! .with_draft(Draft::Draft7)
//! .compile(&schema)?;
//! let result = compiled.validate(&instance);
//! if let Err(errors) = result {
//! for error in errors {
//! println!("Validation error: {}", error)
//! }
//! }
//! Ok(())
//! }
//!
//! ```
#![warn(
clippy::cast_possible_truncation,
@ -57,7 +82,7 @@ mod primitive_type;
mod resolver;
mod schemas;
mod validator;
pub use compilation::JSONSchema;
pub use compilation::{options::CompilationOptions, JSONSchema};
pub use error::{CompilationError, ErrorIterator, ValidationError};
pub use schemas::Draft;
use serde_json::Value;
@ -76,7 +101,7 @@ use serde_json::Value;
#[must_use]
#[inline]
pub fn is_valid(schema: &Value, instance: &Value) -> bool {
let compiled = JSONSchema::compile(schema, None).expect("Invalid schema");
let compiled = JSONSchema::compile(schema).expect("Invalid schema");
compiled.is_valid(instance)
}
@ -86,7 +111,7 @@ mod tests_util {
use serde_json::Value;
pub(crate) fn is_not_valid(schema: Value, instance: Value) {
let compiled = JSONSchema::compile(&schema, None).unwrap();
let compiled = JSONSchema::compile(&schema).unwrap();
assert!(!compiled.is_valid(&instance), "{} should not be valid");
assert!(
compiled.validate(&instance).is_err(),
@ -97,7 +122,7 @@ mod tests_util {
#[cfg(test)]
mod tests {
use super::*;
use super::is_valid;
use serde_json::json;
#[test]

View File

@ -1,4 +1,4 @@
use crate::{compilation::CompilationContext, keywords};
use crate::{compilation::context::CompilationContext, keywords};
use serde_json::{Map, Value};
/// JSON Schema Draft version

View File

@ -14,7 +14,10 @@ fn test_draft(_server_address: &str, test_case: TestCase) {
_ => panic!("Unsupported draft"),
};
let compiled = JSONSchema::compile(&test_case.schema, Some(draft_version)).unwrap();
let compiled = JSONSchema::options()
.with_draft(draft_version)
.compile(&test_case.schema)
.unwrap();
let result = compiled.validate(&test_case.instance);