feat: Support Draft 2019-09

This commit is contained in:
Dmitry Dygalo 2020-05-14 20:44:37 +02:00
parent 9ff2dfd727
commit 9410582289
No known key found for this signature in database
GPG Key ID: E2FDD4885D799724
5 changed files with 132 additions and 1 deletions

View File

@ -59,6 +59,7 @@ pub fn test_draft(input: TokenStream) -> TokenStream {
let dir = Path::new(&test_case.dir_name);
let draft = test_case
.dir_name
.replace("-", "_")
.trim_end_matches('/')
.split('/')
.last()
@ -192,7 +193,7 @@ fn render_test(
should_ignore: bool,
) -> TokenStream2 {
let test_case_name_ident = string_to_ident(test_case_name);
let version_ident = string_to_ident(&draft.to_title_case());
let version_ident = string_to_ident(&draft.to_title_case().replace(" ", ""));
let maybe_ignore_attr: Option<syn::Attribute> = if should_ignore {
Some(syn::parse_quote! { #[ignore] })
} else {

View File

@ -0,0 +1,90 @@
use super::{CompilationResult, Validate};
use crate::compilation::{CompilationContext, JSONSchema};
use crate::error::{no_error, CompilationError, ErrorIterator, ValidationError};
use serde_json::{Map, Value};
use std::collections::{HashMap, HashSet};
pub struct DependentRequiredValidator {
dependent: HashMap<String, Vec<String>>,
}
impl DependentRequiredValidator {
pub(crate) fn compile(schema: &Value) -> CompilationResult {
match schema {
Value::Object(items) => {
let mut dependent = HashMap::with_capacity(items.len());
for (key, value) in items {
match value {
Value::Array(required) => {
let capacity = required.len();
let dependent_required = dependent
.entry(key.clone())
.or_insert_with(|| Vec::with_capacity(capacity));
let mut seen = HashSet::with_capacity(capacity);
for item in required {
match item {
Value::String(string) => {
if seen.insert(string) {
dependent_required.push(string.clone())
} else {
return Err(CompilationError::SchemaError);
}
}
_ => return Err(CompilationError::SchemaError),
}
}
}
_ => return Err(CompilationError::SchemaError),
}
}
Ok(Box::new(DependentRequiredValidator { dependent }))
}
_ => Err(CompilationError::SchemaError),
}
}
}
impl Validate for DependentRequiredValidator {
fn validate<'a>(&self, _: &'a JSONSchema, instance: &'a Value) -> ErrorIterator<'a> {
if let Value::Object(item) = instance {
for (property_name, dependent) in self.dependent.iter() {
if item.contains_key(property_name) {
for required in dependent.iter() {
if !item.contains_key(required) {
// Might be more verbose and specify "why" it is required
return ValidationError::required(required.clone());
}
}
}
}
return no_error();
}
no_error()
}
fn is_valid(&self, _: &JSONSchema, instance: &Value) -> bool {
if let Value::Object(item) = instance {
return self.dependent.iter().all(|(property_name, dependent)| {
// Seems like it could be done with `filter`
if item.contains_key(property_name) {
dependent.iter().all(|required| item.contains_key(required))
} else {
true
}
});
}
true
}
fn name(&self) -> String {
format!("<required: {:?}>", self.dependent)
}
}
pub(crate) fn compile(
_: &Map<String, Value>,
schema: &Value,
_: &CompilationContext,
) -> Option<CompilationResult> {
Some(DependentRequiredValidator::compile(schema))
}

View File

@ -7,6 +7,7 @@ pub(crate) mod const_;
pub(crate) mod contains;
pub(crate) mod content;
pub(crate) mod dependencies;
pub(crate) mod dependent_required;
pub(crate) mod enum_;
pub(crate) mod exclusive_maximum;
pub(crate) mod exclusive_minimum;

View File

@ -8,6 +8,7 @@ pub enum Draft {
Draft4,
Draft6,
Draft7,
Draft201909,
}
type CompileFunc =
@ -16,6 +17,43 @@ type CompileFunc =
impl Draft {
pub(crate) fn get_validator(self, keyword: &str) -> Option<CompileFunc> {
match self {
Draft::Draft201909 => match keyword {
"additionalItems" => Some(keywords::additional_items::compile),
"additionalProperties" => Some(keywords::additional_properties::compile),
"allOf" => Some(keywords::all_of::compile),
"anyOf" => Some(keywords::any_of::compile),
"const" => Some(keywords::const_::compile),
"contains" => Some(keywords::contains::compile),
"contentMediaType" => Some(keywords::content::compile_media_type),
"contentEncoding" => Some(keywords::content::compile_content_encoding),
"dependencies" => Some(keywords::dependencies::compile),
"dependentRequired" => Some(keywords::dependent_required::compile),
"enum" => Some(keywords::enum_::compile),
"exclusiveMaximum" => Some(keywords::exclusive_maximum::compile),
"exclusiveMinimum" => Some(keywords::exclusive_minimum::compile),
"format" => Some(keywords::format::compile),
"if" => Some(keywords::if_::compile),
"items" => Some(keywords::items::compile),
"maximum" => Some(keywords::maximum::compile),
"maxItems" => Some(keywords::max_items::compile),
"maxLength" => Some(keywords::max_length::compile),
"maxProperties" => Some(keywords::max_properties::compile),
"minimum" => Some(keywords::minimum::compile),
"minItems" => Some(keywords::min_items::compile),
"minLength" => Some(keywords::min_length::compile),
"minProperties" => Some(keywords::min_properties::compile),
"multipleOf" => Some(keywords::multiple_of::compile),
"not" => Some(keywords::not::compile),
"oneOf" => Some(keywords::one_of::compile),
"pattern" => Some(keywords::pattern::compile),
"patternProperties" => Some(keywords::pattern_properties::compile),
"properties" => Some(keywords::properties::compile),
"propertyNames" => Some(keywords::property_names::compile),
"required" => Some(keywords::required::compile),
"type" => Some(keywords::type_::compile),
"uniqueItems" => Some(keywords::unique_items::compile),
_ => None,
},
Draft::Draft7 => match keyword {
"additionalItems" => Some(keywords::additional_items::compile),
"additionalProperties" => Some(keywords::additional_properties::compile),

View File

@ -3,3 +3,4 @@ use draft::test_draft;
test_draft!("tests/suite/tests/draft4/", {"optional_bignum_0_0", "optional_bignum_2_0"});
test_draft!("tests/suite/tests/draft6/");
test_draft!("tests/suite/tests/draft7/");
test_draft!("tests/suite/tests/draft2019-09/");