feat: Add cache for schemas
This commit is contained in:
parent
b11b409cc4
commit
0080bf89af
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Cache for documents loaded via the `$ref` keyword. [#75](https://github.com/Stranger6667/jsonschema-rs/issues/75)
|
||||||
|
|
||||||
### Performance
|
### Performance
|
||||||
|
|
||||||
- Enum validation for input values that have a type that is not present among the enum variants. [#80](https://github.com/Stranger6667/jsonschema-rs/issues/80)
|
- Enum validation for input values that have a type that is not present among the enum variants. [#80](https://github.com/Stranger6667/jsonschema-rs/issues/80)
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Cache for documents loaded via the `$ref` keyword. [#75](https://github.com/Stranger6667/jsonschema-rs/issues/75)
|
||||||
|
|
||||||
### Performance
|
### Performance
|
||||||
|
|
||||||
- Enum validation for input values that have a type that is not present among the enum variants. [#80](https://github.com/Stranger6667/jsonschema-rs/issues/80)
|
- Enum validation for input values that have a type that is not present among the enum variants. [#80](https://github.com/Stranger6667/jsonschema-rs/issues/80)
|
||||||
|
|
|
@ -22,6 +22,7 @@ pub struct CompilationOptions {
|
||||||
content_media_type_checks: HashMap<&'static str, Option<ContentMediaTypeCheckType>>,
|
content_media_type_checks: HashMap<&'static str, Option<ContentMediaTypeCheckType>>,
|
||||||
content_encoding_checks_and_converters:
|
content_encoding_checks_and_converters:
|
||||||
HashMap<&'static str, Option<(ContentEncodingCheckType, ContentEncodingConverterType)>>,
|
HashMap<&'static str, Option<(ContentEncodingCheckType, ContentEncodingConverterType)>>,
|
||||||
|
store: HashMap<String, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompilationOptions {
|
impl CompilationOptions {
|
||||||
|
@ -55,7 +56,7 @@ impl CompilationOptions {
|
||||||
Some(url) => url::Url::parse(url)?,
|
Some(url) => url::Url::parse(url)?,
|
||||||
None => DEFAULT_SCOPE.clone(),
|
None => DEFAULT_SCOPE.clone(),
|
||||||
};
|
};
|
||||||
let resolver = Resolver::new(draft, &scope, schema)?;
|
let resolver = Resolver::new(draft, &scope, schema, self.store.clone())?;
|
||||||
let context = CompilationContext::new(scope, processed_config);
|
let context = CompilationContext::new(scope, processed_config);
|
||||||
|
|
||||||
let mut validators = compile_validators(schema, &context)?;
|
let mut validators = compile_validators(schema, &context)?;
|
||||||
|
@ -251,6 +252,13 @@ impl CompilationOptions {
|
||||||
.insert(content_encoding, None);
|
.insert(content_encoding, None);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a new document to the store. It works as a cache to avoid making additional network
|
||||||
|
/// calls to remote schemas via the `$ref` keyword.
|
||||||
|
pub fn with_document(mut self, id: String, document: Value) -> Self {
|
||||||
|
self.store.insert(id, document);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for CompilationOptions {
|
impl fmt::Debug for CompilationOptions {
|
||||||
|
@ -270,6 +278,7 @@ impl fmt::Debug for CompilationOptions {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::CompilationOptions;
|
use super::CompilationOptions;
|
||||||
use crate::schemas::Draft;
|
use crate::schemas::Draft;
|
||||||
|
use crate::JSONSchema;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
|
|
||||||
|
@ -287,4 +296,18 @@ mod tests {
|
||||||
let compiled = options.compile(schema).unwrap();
|
let compiled = options.compile(schema).unwrap();
|
||||||
compiled.context.config.draft()
|
compiled.context.config.draft()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_document() {
|
||||||
|
let schema = json!({"$ref": "http://example.json/schema.json#/rule"});
|
||||||
|
let compiled = JSONSchema::options()
|
||||||
|
.with_document(
|
||||||
|
"http://example.json/schema.json".to_string(),
|
||||||
|
json!({"rule": {"minLength": 5}}),
|
||||||
|
)
|
||||||
|
.compile(&schema)
|
||||||
|
.unwrap();
|
||||||
|
assert!(!compiled.is_valid(&json!("foo")));
|
||||||
|
assert!(compiled.is_valid(&json!("foobar")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::{
|
||||||
error::{CompilationError, ValidationError},
|
error::{CompilationError, ValidationError},
|
||||||
schemas::{id_of, Draft},
|
schemas::{id_of, Draft},
|
||||||
};
|
};
|
||||||
|
use parking_lot::RwLock;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{borrow::Cow, collections::HashMap};
|
use std::{borrow::Cow, collections::HashMap};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -15,6 +16,7 @@ pub(crate) struct Resolver<'a> {
|
||||||
// canonical_id is composed with the root document id
|
// canonical_id is composed with the root document id
|
||||||
// (if not specified, then `DEFAULT_ROOT_URL` is used for this purpose)
|
// (if not specified, then `DEFAULT_ROOT_URL` is used for this purpose)
|
||||||
schemas: HashMap<String, &'a Value>,
|
schemas: HashMap<String, &'a Value>,
|
||||||
|
store: RwLock<HashMap<String, Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Resolver<'a> {
|
impl<'a> Resolver<'a> {
|
||||||
|
@ -22,6 +24,7 @@ impl<'a> Resolver<'a> {
|
||||||
draft: Draft,
|
draft: Draft,
|
||||||
scope: &Url,
|
scope: &Url,
|
||||||
schema: &'a Value,
|
schema: &'a Value,
|
||||||
|
store: HashMap<String, Value>,
|
||||||
) -> Result<Resolver<'a>, CompilationError> {
|
) -> Result<Resolver<'a>, CompilationError> {
|
||||||
let mut schemas = HashMap::new();
|
let mut schemas = HashMap::new();
|
||||||
// traverse the schema and store all named ones under their canonical ids
|
// traverse the schema and store all named ones under their canonical ids
|
||||||
|
@ -29,7 +32,10 @@ impl<'a> Resolver<'a> {
|
||||||
schemas.insert(id, schema);
|
schemas.insert(id, schema);
|
||||||
None
|
None
|
||||||
})?;
|
})?;
|
||||||
Ok(Resolver { schemas })
|
Ok(Resolver {
|
||||||
|
schemas,
|
||||||
|
store: RwLock::new(store),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a document for the given `url`.
|
/// Load a document for the given `url`.
|
||||||
|
@ -40,24 +46,33 @@ impl<'a> Resolver<'a> {
|
||||||
fn resolve_url(&self, url: &Url, schema: &'a Value) -> Result<Cow<'a, Value>, ValidationError> {
|
fn resolve_url(&self, url: &Url, schema: &'a Value) -> Result<Cow<'a, Value>, ValidationError> {
|
||||||
match url.as_str() {
|
match url.as_str() {
|
||||||
DEFAULT_ROOT_URL => Ok(Cow::Borrowed(schema)),
|
DEFAULT_ROOT_URL => Ok(Cow::Borrowed(schema)),
|
||||||
url_str => match self.schemas.get(url_str) {
|
url_str => {
|
||||||
Some(value) => Ok(Cow::Borrowed(value)),
|
if let Some(cached) = self.store.read().get(url_str) {
|
||||||
None => match url.scheme() {
|
return Ok(Cow::Owned(cached.clone()));
|
||||||
"http" | "https" => {
|
}
|
||||||
#[cfg(any(feature = "reqwest", test))]
|
|
||||||
{
|
match self.schemas.get(url_str) {
|
||||||
let response = reqwest::blocking::get(url.as_str())?;
|
Some(value) => Ok(Cow::Borrowed(value)),
|
||||||
let document: Value = response.json()?;
|
None => match url.scheme() {
|
||||||
Ok(Cow::Owned(document))
|
"http" | "https" => {
|
||||||
|
#[cfg(any(feature = "reqwest", test))]
|
||||||
|
{
|
||||||
|
let response = reqwest::blocking::get(url.as_str())?;
|
||||||
|
let document: Value = response.json()?;
|
||||||
|
self.store
|
||||||
|
.write()
|
||||||
|
.insert(url_str.to_string(), document.clone());
|
||||||
|
Ok(Cow::Owned(document))
|
||||||
|
}
|
||||||
|
#[cfg(not(any(feature = "reqwest", test)))]
|
||||||
|
panic!("trying to resolve an http(s), but reqwest support has not been included");
|
||||||
}
|
}
|
||||||
#[cfg(not(any(feature = "reqwest", test)))]
|
http_scheme => Err(ValidationError::unknown_reference_scheme(
|
||||||
panic!("trying to resolve an http(s), but reqwest support has not been included");
|
http_scheme.to_owned(),
|
||||||
}
|
)),
|
||||||
http_scheme => Err(ValidationError::unknown_reference_scheme(
|
},
|
||||||
http_scheme.to_owned(),
|
}
|
||||||
)),
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) fn resolve_fragment(
|
pub(crate) fn resolve_fragment(
|
||||||
|
@ -216,6 +231,7 @@ mod tests {
|
||||||
Draft::Draft7,
|
Draft::Draft7,
|
||||||
&Url::parse("json-schema:///").unwrap(),
|
&Url::parse("json-schema:///").unwrap(),
|
||||||
schema,
|
schema,
|
||||||
|
HashMap::new(),
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue