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