feat: implement basic output format
This commit is contained in:
parent
e45ebe6c34
commit
9daae35d2d
|
@ -22,6 +22,7 @@ draft201909 = []
|
|||
|
||||
[dependencies]
|
||||
serde_json = "1"
|
||||
serde = "1"
|
||||
url = "2"
|
||||
lazy_static = "1"
|
||||
percent-encoding = "2"
|
||||
|
@ -32,7 +33,7 @@ chrono = ">= 0.2"
|
|||
reqwest = { version = ">= 0.10", features = ["blocking", "json"], optional = true}
|
||||
parking_lot = ">= 0.1"
|
||||
num-cmp = ">= 0.1"
|
||||
ahash = "0.7"
|
||||
ahash = { version = "0.7", features = ["serde"] }
|
||||
structopt = { version = ">= 0.3", optional = true }
|
||||
itoa = "0.4"
|
||||
fraction = { version = "0.8", default-features = false, features = ["with-bigint"] }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::options::CompilationOptions;
|
||||
use crate::{
|
||||
compilation::DEFAULT_SCOPE,
|
||||
paths::{InstancePath, JSONPointer, PathChunk},
|
||||
schemas,
|
||||
};
|
||||
|
@ -7,18 +8,74 @@ use serde_json::Value;
|
|||
use std::borrow::Cow;
|
||||
use url::{ParseError, Url};
|
||||
|
||||
static DEFAULT_SCHEME: &str = "json-schema";
|
||||
|
||||
/// Context holds information about used draft and current scope.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct CompilationContext<'a> {
|
||||
pub(crate) scope: Cow<'a, Url>,
|
||||
base_uri: BaseUri<'a>,
|
||||
pub(crate) config: &'a CompilationOptions,
|
||||
pub(crate) schema_path: InstancePath<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum BaseUri<'a> {
|
||||
/// A base URL was specified, either because we know a reasonable base URI where we retrieved the
|
||||
/// schema or because a URI was specified via $id
|
||||
Known(Cow<'a, Url>),
|
||||
/// We don't know a base URI for this schema
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl<'a> BaseUri<'a> {
|
||||
pub(crate) fn with_new_scope(&self, new_scope: &str) -> Result<Self, ParseError> {
|
||||
let options = match self {
|
||||
BaseUri::Known(u) => Url::options().base_url(Some(u)),
|
||||
BaseUri::Unknown => Url::options().base_url(Some(&DEFAULT_SCOPE)),
|
||||
};
|
||||
Ok(options.parse(new_scope)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Option<Url>> for BaseUri<'a> {
|
||||
fn from(u: Option<Url>) -> Self {
|
||||
u.map_or(BaseUri::Unknown, |u| u.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Url> for BaseUri<'a> {
|
||||
fn from(u: &'a Url) -> Self {
|
||||
if u.scheme() == DEFAULT_SCHEME {
|
||||
BaseUri::Unknown
|
||||
} else {
|
||||
BaseUri::Known(Cow::Borrowed(u))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Url> for BaseUri<'a> {
|
||||
fn from(u: Url) -> Self {
|
||||
if u.scheme() == DEFAULT_SCHEME {
|
||||
BaseUri::Unknown
|
||||
} else {
|
||||
BaseUri::Known(Cow::Owned(u))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a BaseUri<'a>> for Cow<'a, Url> {
|
||||
fn from(uri: &BaseUri<'a>) -> Self {
|
||||
match uri {
|
||||
BaseUri::Unknown => Cow::Borrowed(&DEFAULT_SCOPE),
|
||||
BaseUri::Known(u) => u.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> CompilationContext<'a> {
|
||||
pub(crate) const fn new(scope: Url, config: &'a CompilationOptions) -> Self {
|
||||
pub(crate) const fn new(scope: BaseUri<'a>, config: &'a CompilationOptions) -> Self {
|
||||
CompilationContext {
|
||||
scope: Cow::Owned(scope),
|
||||
base_uri: scope,
|
||||
config,
|
||||
schema_path: InstancePath::new(),
|
||||
}
|
||||
|
@ -37,15 +94,14 @@ impl<'a> CompilationContext<'a> {
|
|||
#[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),
|
||||
base_uri: self.base_uri.with_new_scope(id)?,
|
||||
config: self.config,
|
||||
schema_path: self.schema_path.clone(),
|
||||
})
|
||||
} else {
|
||||
Ok(CompilationContext {
|
||||
scope: Cow::Borrowed(self.scope.as_ref()),
|
||||
base_uri: self.base_uri.clone(),
|
||||
config: self.config,
|
||||
schema_path: self.schema_path.clone(),
|
||||
})
|
||||
|
@ -56,7 +112,7 @@ impl<'a> CompilationContext<'a> {
|
|||
pub(crate) fn with_path(&'a self, chunk: impl Into<PathChunk>) -> Self {
|
||||
let schema_path = self.schema_path.push(chunk);
|
||||
CompilationContext {
|
||||
scope: Cow::Borrowed(self.scope.as_ref()),
|
||||
base_uri: self.base_uri.clone(),
|
||||
config: self.config,
|
||||
schema_path,
|
||||
}
|
||||
|
@ -76,6 +132,16 @@ impl<'a> CompilationContext<'a> {
|
|||
|
||||
/// 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)
|
||||
let cowbase: Cow<Url> = (&self.base_uri).into();
|
||||
Url::options()
|
||||
.base_url(Some(cowbase.as_ref()))
|
||||
.parse(reference)
|
||||
}
|
||||
|
||||
pub(crate) fn base_uri(&self) -> Option<Url> {
|
||||
match &self.base_uri {
|
||||
BaseUri::Known(u) => Some(u.as_ref().clone()),
|
||||
BaseUri::Unknown => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,10 @@ pub(crate) mod context;
|
|||
pub(crate) mod options;
|
||||
|
||||
use crate::{
|
||||
error::ErrorIterator, keywords, paths::InstancePath, resolver::Resolver, validator::Validators,
|
||||
Draft, ValidationError,
|
||||
error::ErrorIterator, keywords, paths::InstancePath, resolver::Resolver,
|
||||
schema_node::SchemaNode, validator::Validate, Draft, Output, ValidationError,
|
||||
};
|
||||
use ahash::AHashMap;
|
||||
use context::CompilationContext;
|
||||
use options::CompilationOptions;
|
||||
use serde_json::Value;
|
||||
|
@ -20,7 +21,7 @@ pub(crate) const DEFAULT_ROOT_URL: &str = "json-schema:///";
|
|||
#[derive(Debug)]
|
||||
pub struct JSONSchema {
|
||||
pub(crate) schema: Arc<Value>,
|
||||
pub(crate) validators: Validators,
|
||||
pub(crate) node: SchemaNode,
|
||||
pub(crate) resolver: Resolver,
|
||||
config: CompilationOptions,
|
||||
}
|
||||
|
@ -61,9 +62,8 @@ impl JSONSchema {
|
|||
pub fn validate<'a>(&'a self, instance: &'a Value) -> Result<(), ErrorIterator<'a>> {
|
||||
let instance_path = InstancePath::new();
|
||||
let mut errors = self
|
||||
.validators
|
||||
.iter()
|
||||
.flat_map(move |validator| validator.validate(self, instance, &instance_path))
|
||||
.node
|
||||
.validate(self, instance, &instance_path)
|
||||
.peekable();
|
||||
if errors.peek().is_none() {
|
||||
Ok(())
|
||||
|
@ -78,9 +78,42 @@ impl JSONSchema {
|
|||
#[must_use]
|
||||
#[inline]
|
||||
pub fn is_valid(&self, instance: &Value) -> bool {
|
||||
self.validators
|
||||
.iter()
|
||||
.all(|validator| validator.is_valid(self, instance))
|
||||
self.node.is_valid(self, instance)
|
||||
}
|
||||
|
||||
/// Apply the schema and return an `Output`. No actual work is done at this point, the
|
||||
/// evaluation of the schema is deferred until a method is called on the `Output`. This is
|
||||
/// because different output formats will have different performance characteristics.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// "basic" output format
|
||||
///
|
||||
/// ```rust
|
||||
/// # use crate::jsonschema::{Draft, Output, BasicOutput, JSONSchema};
|
||||
/// let schema_json = serde_json::json!({
|
||||
/// "title": "string value",
|
||||
/// "type": "string"
|
||||
/// });
|
||||
/// let instance = serde_json::json!{"some string"};
|
||||
/// let schema = JSONSchema::options().compile(&schema_json).unwrap();
|
||||
/// let output: BasicOutput = schema.apply(&instance).basic();
|
||||
/// let output_json = serde_json::to_value(output).unwrap();
|
||||
/// assert_eq!(output_json, serde_json::json!({
|
||||
/// "valid": true,
|
||||
/// "annotations": [
|
||||
/// {
|
||||
/// "keywordLocation": "",
|
||||
/// "instanceLocation": "",
|
||||
/// "annotations": {
|
||||
/// "title": "string value"
|
||||
/// }
|
||||
/// }
|
||||
/// ]
|
||||
/// }));
|
||||
/// ```
|
||||
pub const fn apply<'a, 'b>(&'a self, instance: &'b Value) -> Output<'a, 'b> {
|
||||
Output::new(self, &self.node, instance)
|
||||
}
|
||||
|
||||
/// The [`Draft`] which this schema was compiled against
|
||||
|
@ -99,34 +132,90 @@ impl JSONSchema {
|
|||
pub(crate) fn compile_validators<'a, 'c>(
|
||||
schema: &'a Value,
|
||||
context: &'c CompilationContext,
|
||||
) -> Result<Validators, ValidationError<'a>> {
|
||||
) -> Result<SchemaNode, ValidationError<'a>> {
|
||||
let context = context.push(schema)?;
|
||||
let relative_path = context.clone().into_pointer();
|
||||
match schema {
|
||||
Value::Bool(value) => match value {
|
||||
true => Ok(vec![]),
|
||||
false => Ok(vec![keywords::boolean::FalseValidator::compile(
|
||||
context.into_pointer(),
|
||||
)
|
||||
.expect("Should always compile")]),
|
||||
true => Ok(SchemaNode::new_from_boolean(&context, None)),
|
||||
false => Ok(SchemaNode::new_from_boolean(
|
||||
&context,
|
||||
Some(
|
||||
keywords::boolean::FalseValidator::compile(relative_path)
|
||||
.expect("Should always compile"),
|
||||
),
|
||||
)),
|
||||
},
|
||||
Value::Object(object) => {
|
||||
if let Some(reference) = object.get("$ref") {
|
||||
let unmatched_keywords = object
|
||||
.iter()
|
||||
.filter_map(|(k, v)| {
|
||||
if k.as_str() != "$ref" {
|
||||
Some((k.clone(), v.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut validators = Vec::new();
|
||||
if let Value::String(reference) = reference {
|
||||
Ok(vec![keywords::ref_::compile(schema, reference, &context)
|
||||
.expect("Should always return Some")?])
|
||||
let validator = keywords::ref_::compile(schema, reference, &context)
|
||||
.expect("Should always return Some")?;
|
||||
validators.push(("$ref".to_string(), validator));
|
||||
Ok(SchemaNode::new_from_keywords(
|
||||
&context,
|
||||
validators,
|
||||
Some(unmatched_keywords),
|
||||
))
|
||||
} else {
|
||||
Err(ValidationError::schema(schema))
|
||||
}
|
||||
} else {
|
||||
let mut validators = Vec::with_capacity(object.len());
|
||||
let mut unmatched_keywords = AHashMap::new();
|
||||
let mut is_if = false;
|
||||
let mut is_props = false;
|
||||
for (keyword, subschema) in object {
|
||||
if let Some(compilation_func) = context.config.draft().get_validator(keyword) {
|
||||
if let Some(validator) = compilation_func(object, subschema, &context) {
|
||||
validators.push(validator?)
|
||||
}
|
||||
if keyword == "if" {
|
||||
is_if = true;
|
||||
}
|
||||
if keyword == "properties"
|
||||
|| keyword == "additionalProperties"
|
||||
|| keyword == "patternProperties"
|
||||
{
|
||||
is_props = true;
|
||||
}
|
||||
if let Some(validator) = context
|
||||
.config
|
||||
.draft()
|
||||
.get_validator(keyword)
|
||||
.and_then(|f| f(object, subschema, &context))
|
||||
{
|
||||
validators.push((keyword.clone(), validator?));
|
||||
} else {
|
||||
unmatched_keywords.insert(keyword.to_string(), subschema.clone());
|
||||
}
|
||||
}
|
||||
Ok(validators)
|
||||
if is_if {
|
||||
unmatched_keywords.remove("then");
|
||||
unmatched_keywords.remove("else");
|
||||
}
|
||||
if is_props {
|
||||
unmatched_keywords.remove("additionalProperties");
|
||||
unmatched_keywords.remove("patternProperties");
|
||||
unmatched_keywords.remove("properties");
|
||||
}
|
||||
let unmatched_keywords = if unmatched_keywords.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(unmatched_keywords)
|
||||
};
|
||||
Ok(SchemaNode::new_from_keywords(
|
||||
&context,
|
||||
validators,
|
||||
unmatched_keywords,
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => Err(ValidationError::schema(schema)),
|
||||
|
@ -159,7 +248,7 @@ mod tests {
|
|||
let value1 = json!("AB");
|
||||
let value2 = json!(1);
|
||||
// And only this validator
|
||||
assert_eq!(compiled.validators.len(), 1);
|
||||
assert_eq!(compiled.node.validators().len(), 1);
|
||||
assert!(compiled.validate(&value1).is_ok());
|
||||
assert!(compiled.validate(&value2).is_err());
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@ impl CompilationOptions {
|
|||
};
|
||||
let schema_json = Arc::new(schema.clone());
|
||||
let resolver = Resolver::new(draft, &scope, schema_json.clone(), self.store.clone())?;
|
||||
let context = CompilationContext::new(scope, &config);
|
||||
let context = CompilationContext::new(scope.into(), &config);
|
||||
|
||||
if self.validate_schema {
|
||||
if let Some(mut errors) = META_SCHEMA_VALIDATORS
|
||||
|
@ -199,12 +199,11 @@ impl CompilationOptions {
|
|||
}
|
||||
}
|
||||
|
||||
let mut validators = compile_validators(schema, &context)?;
|
||||
validators.shrink_to_fit();
|
||||
let node = compile_validators(schema, &context)?;
|
||||
|
||||
Ok(JSONSchema {
|
||||
schema: schema_json,
|
||||
validators,
|
||||
node,
|
||||
resolver,
|
||||
config,
|
||||
})
|
||||
|
|
|
@ -3,12 +3,13 @@ use crate::{
|
|||
error::{error, no_error, ErrorIterator, ValidationError},
|
||||
keywords::{boolean::FalseValidator, CompilationResult},
|
||||
paths::{InstancePath, JSONPointer},
|
||||
validator::{format_validators, Validate, Validators},
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_validators, Validate},
|
||||
};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub(crate) struct AdditionalItemsObjectValidator {
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
items_count: usize,
|
||||
}
|
||||
impl AdditionalItemsObjectValidator {
|
||||
|
@ -18,9 +19,9 @@ impl AdditionalItemsObjectValidator {
|
|||
items_count: usize,
|
||||
context: &CompilationContext,
|
||||
) -> CompilationResult<'a> {
|
||||
let validators = compile_validators(schema, context)?;
|
||||
let node = compile_validators(schema, context)?;
|
||||
Ok(Box::new(AdditionalItemsObjectValidator {
|
||||
validators,
|
||||
node,
|
||||
items_count,
|
||||
}))
|
||||
}
|
||||
|
@ -28,16 +29,16 @@ impl AdditionalItemsObjectValidator {
|
|||
impl Validate for AdditionalItemsObjectValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
if let Value::Array(items) = instance {
|
||||
items.iter().skip(self.items_count).all(|item| {
|
||||
self.validators
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, item))
|
||||
})
|
||||
items
|
||||
.iter()
|
||||
.skip(self.items_count)
|
||||
.all(|item| self.node.is_valid(schema, item))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
|
@ -49,11 +50,7 @@ impl Validate for AdditionalItemsObjectValidator {
|
|||
.iter()
|
||||
.enumerate()
|
||||
.skip(self.items_count)
|
||||
.flat_map(|(idx, item)| {
|
||||
self.validators.iter().flat_map(move |validator| {
|
||||
validator.validate(schema, item, &instance_path.push(idx))
|
||||
})
|
||||
})
|
||||
.flat_map(|(idx, item)| self.node.validate(schema, item, &instance_path.push(idx)))
|
||||
.collect();
|
||||
Box::new(errors.into_iter())
|
||||
} else {
|
||||
|
@ -67,7 +64,7 @@ impl core::fmt::Display for AdditionalItemsObjectValidator {
|
|||
write!(
|
||||
f,
|
||||
"additionalItems: {}",
|
||||
format_validators(&self.validators)
|
||||
format_validators(self.node.validators())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,41 +10,43 @@ use crate::{
|
|||
compilation::{compile_validators, context::CompilationContext, JSONSchema},
|
||||
error::{error, no_error, ErrorIterator, ValidationError},
|
||||
keywords::CompilationResult,
|
||||
paths::{InstancePath, JSONPointer},
|
||||
validator::{format_validators, Validate, Validators},
|
||||
output::{Annotations, BasicOutput, OutputUnit},
|
||||
paths::{AbsolutePath, InstancePath, JSONPointer},
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_validators, PartialApplication, Validate},
|
||||
};
|
||||
use ahash::AHashMap;
|
||||
use fancy_regex::Regex;
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub(crate) type PatternedValidators = Vec<(Regex, Validators)>;
|
||||
pub(crate) type PatternedValidators = Vec<(Regex, SchemaNode)>;
|
||||
|
||||
/// Provide mapping API to get validators associated with a property from the underlying storage.
|
||||
pub(crate) trait PropertiesValidatorsMap: Send + Sync {
|
||||
fn get_validator(&self, property: &str) -> Option<&Validators>;
|
||||
fn get_key_validator(&self, property: &str) -> Option<(&String, &Validators)>;
|
||||
fn get_validator(&self, property: &str) -> Option<&SchemaNode>;
|
||||
fn get_key_validator(&self, property: &str) -> Option<(&String, &SchemaNode)>;
|
||||
}
|
||||
|
||||
// Iterating over a small vector and comparing strings is faster than a map lookup
|
||||
const MAP_SIZE_THRESHOLD: usize = 40;
|
||||
pub(crate) type SmallValidatorsMap = Vec<(String, Validators)>;
|
||||
pub(crate) type BigValidatorsMap = AHashMap<String, Validators>;
|
||||
pub(crate) type SmallValidatorsMap = Vec<(String, SchemaNode)>;
|
||||
pub(crate) type BigValidatorsMap = AHashMap<String, SchemaNode>;
|
||||
|
||||
impl PropertiesValidatorsMap for SmallValidatorsMap {
|
||||
#[inline]
|
||||
fn get_validator(&self, property: &str) -> Option<&Validators> {
|
||||
for (prop, validators) in self {
|
||||
fn get_validator(&self, property: &str) -> Option<&SchemaNode> {
|
||||
for (prop, node) in self {
|
||||
if prop == property {
|
||||
return Some(validators);
|
||||
return Some(node);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
#[inline]
|
||||
fn get_key_validator(&self, property: &str) -> Option<(&String, &Validators)> {
|
||||
for (prop, validators) in self {
|
||||
fn get_key_validator(&self, property: &str) -> Option<(&String, &SchemaNode)> {
|
||||
for (prop, node) in self {
|
||||
if prop == property {
|
||||
return Some((prop, validators));
|
||||
return Some((prop, node));
|
||||
}
|
||||
}
|
||||
None
|
||||
|
@ -53,12 +55,12 @@ impl PropertiesValidatorsMap for SmallValidatorsMap {
|
|||
|
||||
impl PropertiesValidatorsMap for BigValidatorsMap {
|
||||
#[inline]
|
||||
fn get_validator(&self, property: &str) -> Option<&Validators> {
|
||||
fn get_validator(&self, property: &str) -> Option<&SchemaNode> {
|
||||
self.get(property)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_key_validator(&self, property: &str) -> Option<(&String, &Validators)> {
|
||||
fn get_key_validator(&self, property: &str) -> Option<(&String, &SchemaNode)> {
|
||||
self.get_key_value(property)
|
||||
}
|
||||
}
|
||||
|
@ -81,16 +83,14 @@ macro_rules! dynamic_map {
|
|||
}};
|
||||
}
|
||||
macro_rules! is_valid {
|
||||
($validators:expr, $schema:ident, $value:ident) => {{
|
||||
$validators
|
||||
.iter()
|
||||
.all(|validator| validator.is_valid($schema, $value))
|
||||
($node:expr, $schema:ident, $value:ident) => {{
|
||||
$node.is_valid($schema, $value)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! is_valid_pattern_schema {
|
||||
($validators:expr, $schema:ident, $value:ident) => {{
|
||||
if is_valid!($validators, $schema, $value) {
|
||||
($node:expr, $schema:ident, $value:ident) => {{
|
||||
if $node.is_valid($schema, $value) {
|
||||
// Matched & valid - check the next pattern
|
||||
continue;
|
||||
}
|
||||
|
@ -103,11 +103,11 @@ macro_rules! is_valid_patterns {
|
|||
($schema:ident, $patterns:expr, $property:ident, $value:ident) => {{
|
||||
// One property may match multiple patterns, therefore we need to check them all
|
||||
let mut has_match = false;
|
||||
for (re, validators) in $patterns {
|
||||
for (re, node) in $patterns {
|
||||
// If there is a match, then the value should match the sub-schema
|
||||
if re.is_match($property).unwrap_or(false) {
|
||||
has_match = true;
|
||||
is_valid_pattern_schema!(validators, $schema, $value)
|
||||
is_valid_pattern_schema!(node, $schema, $value)
|
||||
}
|
||||
}
|
||||
if !has_match {
|
||||
|
@ -118,11 +118,9 @@ macro_rules! is_valid_patterns {
|
|||
}
|
||||
|
||||
macro_rules! validate {
|
||||
($validators:expr, $schema:ident, $value:ident, $instance_path:expr, $property_name:expr) => {{
|
||||
($node:expr, $schema:ident, $value:ident, $instance_path:expr, $property_name:expr) => {{
|
||||
let instance_path = $instance_path.push($property_name.clone());
|
||||
$validators
|
||||
.iter()
|
||||
.flat_map(move |validator| validator.validate($schema, $value, &instance_path))
|
||||
$node.validate($schema, $value, &instance_path)
|
||||
}};
|
||||
}
|
||||
|
||||
|
@ -173,7 +171,7 @@ fn compile_big_map<'a>(
|
|||
/// }
|
||||
/// ```
|
||||
pub(crate) struct AdditionalPropertiesValidator {
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
}
|
||||
impl AdditionalPropertiesValidator {
|
||||
#[inline]
|
||||
|
@ -183,22 +181,20 @@ impl AdditionalPropertiesValidator {
|
|||
) -> CompilationResult<'a> {
|
||||
let keyword_context = context.with_path("additionalProperties");
|
||||
Ok(Box::new(AdditionalPropertiesValidator {
|
||||
validators: compile_validators(schema, &keyword_context)?,
|
||||
node: compile_validators(schema, &keyword_context)?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
impl Validate for AdditionalPropertiesValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
if let Value::Object(item) = instance {
|
||||
for value in item.values() {
|
||||
if !is_valid!(self.validators, schema, value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
item.values().all(|i| self.node.is_valid(schema, i))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
|
@ -208,15 +204,35 @@ impl Validate for AdditionalPropertiesValidator {
|
|||
if let Value::Object(item) = instance {
|
||||
let errors: Vec<_> = item
|
||||
.iter()
|
||||
.flat_map(|(name, value)| {
|
||||
validate!(self.validators, schema, value, instance_path, name)
|
||||
})
|
||||
.flat_map(|(name, value)| validate!(self.node, schema, value, instance_path, name))
|
||||
.collect();
|
||||
Box::new(errors.into_iter())
|
||||
} else {
|
||||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
if let Value::Object(item) = instance {
|
||||
let mut matched_props = Vec::with_capacity(item.len());
|
||||
let mut output = BasicOutput::default();
|
||||
for (name, value) in item.iter() {
|
||||
let path = instance_path.push(name.to_string());
|
||||
output += self.node.apply_rooted(schema, value, &path);
|
||||
matched_props.push(name.clone());
|
||||
}
|
||||
let mut result: PartialApplication = output.into();
|
||||
result.annotate(serde_json::Value::from(matched_props).into());
|
||||
result
|
||||
} else {
|
||||
PartialApplication::valid_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for AdditionalPropertiesValidator {
|
||||
|
@ -224,7 +240,7 @@ impl core::fmt::Display for AdditionalPropertiesValidator {
|
|||
write!(
|
||||
f,
|
||||
"additionalProperties: {}",
|
||||
format_validators(&self.validators)
|
||||
format_validators(self.node.validators())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -334,8 +350,8 @@ impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesNotEmptyFalseV
|
|||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
if let Value::Object(item) = instance {
|
||||
for (property, value) in item {
|
||||
if let Some(validators) = self.properties.get_validator(property) {
|
||||
is_valid_pattern_schema!(validators, schema, value)
|
||||
if let Some(node) = self.properties.get_validator(property) {
|
||||
is_valid_pattern_schema!(node, schema, value)
|
||||
}
|
||||
// No extra properties are allowed
|
||||
return false;
|
||||
|
@ -354,9 +370,9 @@ impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesNotEmptyFalseV
|
|||
let mut errors = vec![];
|
||||
let mut unexpected = vec![];
|
||||
for (property, value) in item {
|
||||
if let Some((name, validators)) = self.properties.get_key_validator(property) {
|
||||
if let Some((name, node)) = self.properties.get_key_validator(property) {
|
||||
// When a property is in `properties`, then it should be VALID
|
||||
errors.extend(validate!(validators, schema, value, instance_path, name));
|
||||
errors.extend(validate!(node, schema, value, instance_path, name));
|
||||
} else {
|
||||
// No extra properties are allowed
|
||||
unexpected.push(property.clone());
|
||||
|
@ -375,6 +391,41 @@ impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesNotEmptyFalseV
|
|||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
if let Value::Object(item) = instance {
|
||||
let mut unexpected = Vec::with_capacity(item.len());
|
||||
let mut output = BasicOutput::default();
|
||||
for (property, value) in item {
|
||||
if let Some((_name, node)) = self.properties.get_key_validator(property) {
|
||||
let path = instance_path.push(property.clone());
|
||||
output += node.apply_rooted(schema, value, &path);
|
||||
} else {
|
||||
unexpected.push(property.clone())
|
||||
}
|
||||
}
|
||||
let mut result: PartialApplication = output.into();
|
||||
if !unexpected.is_empty() {
|
||||
result.mark_errored(
|
||||
ValidationError::additional_properties(
|
||||
self.schema_path.clone(),
|
||||
instance_path.into(),
|
||||
instance,
|
||||
unexpected,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
result
|
||||
} else {
|
||||
PartialApplication::valid_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: PropertiesValidatorsMap> core::fmt::Display
|
||||
|
@ -405,7 +456,7 @@ impl<M: PropertiesValidatorsMap> core::fmt::Display
|
|||
/// }
|
||||
/// ```
|
||||
pub(crate) struct AdditionalPropertiesNotEmptyValidator<M: PropertiesValidatorsMap> {
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
properties: M,
|
||||
}
|
||||
impl AdditionalPropertiesNotEmptyValidator<SmallValidatorsMap> {
|
||||
|
@ -418,7 +469,7 @@ impl AdditionalPropertiesNotEmptyValidator<SmallValidatorsMap> {
|
|||
let keyword_context = context.with_path("additionalProperties");
|
||||
Ok(Box::new(AdditionalPropertiesNotEmptyValidator {
|
||||
properties: compile_small_map(map, context)?,
|
||||
validators: compile_validators(schema, &keyword_context)?,
|
||||
node: compile_validators(schema, &keyword_context)?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -432,7 +483,7 @@ impl AdditionalPropertiesNotEmptyValidator<BigValidatorsMap> {
|
|||
let keyword_context = context.with_path("additionalProperties");
|
||||
Ok(Box::new(AdditionalPropertiesNotEmptyValidator {
|
||||
properties: compile_big_map(map, context)?,
|
||||
validators: compile_validators(schema, &keyword_context)?,
|
||||
node: compile_validators(schema, &keyword_context)?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -443,10 +494,8 @@ impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesNotEmptyValida
|
|||
if let Some(property_validators) = self.properties.get_validator(property) {
|
||||
is_valid_pattern_schema!(property_validators, schema, value)
|
||||
}
|
||||
for validator in &self.validators {
|
||||
if !validator.is_valid(schema, value) {
|
||||
return false;
|
||||
}
|
||||
if !self.node.is_valid(schema, value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -473,13 +522,7 @@ impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesNotEmptyValida
|
|||
name
|
||||
))
|
||||
} else {
|
||||
errors.extend(validate!(
|
||||
self.validators,
|
||||
schema,
|
||||
value,
|
||||
instance_path,
|
||||
property
|
||||
))
|
||||
errors.extend(validate!(self.node, schema, value, instance_path, property))
|
||||
}
|
||||
}
|
||||
Box::new(errors.into_iter())
|
||||
|
@ -487,6 +530,36 @@ impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesNotEmptyValida
|
|||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
if let Value::Object(map) = instance {
|
||||
let mut matched_propnames = Vec::with_capacity(map.len());
|
||||
let mut output = BasicOutput::default();
|
||||
for (property, value) in map {
|
||||
let path = instance_path.push(property.clone());
|
||||
if let Some((_name, property_validators)) =
|
||||
self.properties.get_key_validator(property)
|
||||
{
|
||||
output += property_validators.apply_rooted(schema, value, &path);
|
||||
} else {
|
||||
output += self.node.apply_rooted(schema, value, &path);
|
||||
matched_propnames.push(property.clone());
|
||||
}
|
||||
}
|
||||
let mut result: PartialApplication = output.into();
|
||||
if !matched_propnames.is_empty() {
|
||||
result.annotate(serde_json::Value::from(matched_propnames).into());
|
||||
}
|
||||
result
|
||||
} else {
|
||||
PartialApplication::valid_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: PropertiesValidatorsMap> core::fmt::Display for AdditionalPropertiesNotEmptyValidator<M> {
|
||||
|
@ -494,7 +567,7 @@ impl<M: PropertiesValidatorsMap> core::fmt::Display for AdditionalPropertiesNotE
|
|||
write!(
|
||||
f,
|
||||
"additionalProperties: {}",
|
||||
format_validators(&self.validators)
|
||||
format_validators(self.node.validators())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -521,8 +594,14 @@ impl<M: PropertiesValidatorsMap> core::fmt::Display for AdditionalPropertiesNotE
|
|||
/// }
|
||||
/// ```
|
||||
pub(crate) struct AdditionalPropertiesWithPatternsValidator {
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
patterns: PatternedValidators,
|
||||
/// We need this because `compile_validators` uses the additionalProperties keyword to compile
|
||||
/// this validator. That means that the schema node which contains this validator has
|
||||
/// "additionalProperties" as it's path. However, we need to produce annotations which have the
|
||||
/// patternProperties keyword as their path so we store the paths here.
|
||||
pattern_keyword_path: JSONPointer,
|
||||
pattern_keyword_absolute_path: Option<AbsolutePath>,
|
||||
}
|
||||
impl AdditionalPropertiesWithPatternsValidator {
|
||||
#[inline]
|
||||
|
@ -532,8 +611,13 @@ impl AdditionalPropertiesWithPatternsValidator {
|
|||
context: &CompilationContext,
|
||||
) -> CompilationResult<'a> {
|
||||
Ok(Box::new(AdditionalPropertiesWithPatternsValidator {
|
||||
validators: compile_validators(schema, context)?,
|
||||
node: compile_validators(schema, &context.with_path("additionalProperties"))?,
|
||||
patterns,
|
||||
pattern_keyword_path: context.as_pointer_with("patternProperties"),
|
||||
pattern_keyword_absolute_path: context
|
||||
.with_path("patternProperties")
|
||||
.base_uri()
|
||||
.map(|u| u.into()),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -542,13 +626,13 @@ impl Validate for AdditionalPropertiesWithPatternsValidator {
|
|||
if let Value::Object(item) = instance {
|
||||
for (property, value) in item.iter() {
|
||||
let mut has_match = false;
|
||||
for (re, validators) in &self.patterns {
|
||||
for (re, node) in &self.patterns {
|
||||
if re.is_match(property).unwrap_or(false) {
|
||||
has_match = true;
|
||||
is_valid_pattern_schema!(validators, schema, value)
|
||||
is_valid_pattern_schema!(node, schema, value)
|
||||
}
|
||||
}
|
||||
if !has_match && !is_valid!(self.validators, schema, value) {
|
||||
if !has_match && !is_valid!(self.node, schema, value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -570,19 +654,13 @@ impl Validate for AdditionalPropertiesWithPatternsValidator {
|
|||
self.patterns
|
||||
.iter()
|
||||
.filter(|(re, _)| re.is_match(property).unwrap_or(false))
|
||||
.flat_map(|(_, validators)| {
|
||||
.flat_map(|(_, node)| {
|
||||
has_match = true;
|
||||
validate!(validators, schema, value, instance_path, property)
|
||||
validate!(node, schema, value, instance_path, property)
|
||||
}),
|
||||
);
|
||||
if !has_match {
|
||||
errors.extend(validate!(
|
||||
self.validators,
|
||||
schema,
|
||||
value,
|
||||
instance_path,
|
||||
property
|
||||
))
|
||||
errors.extend(validate!(self.node, schema, value, instance_path, property))
|
||||
}
|
||||
}
|
||||
Box::new(errors.into_iter())
|
||||
|
@ -590,6 +668,50 @@ impl Validate for AdditionalPropertiesWithPatternsValidator {
|
|||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
if let Value::Object(item) = instance {
|
||||
let mut output = BasicOutput::default();
|
||||
let mut pattern_matched_propnames = Vec::with_capacity(item.len());
|
||||
let mut additional_matched_propnames = Vec::with_capacity(item.len());
|
||||
for (property, value) in item {
|
||||
let path = instance_path.push(property.clone());
|
||||
let mut has_match = false;
|
||||
for (pattern, node) in &self.patterns {
|
||||
if pattern.is_match(property).unwrap_or(false) {
|
||||
has_match = true;
|
||||
pattern_matched_propnames.push(property.clone());
|
||||
output += node.apply_rooted(schema, value, &path)
|
||||
}
|
||||
}
|
||||
if !has_match {
|
||||
additional_matched_propnames.push(property.clone());
|
||||
output += self.node.apply_rooted(schema, value, &path)
|
||||
}
|
||||
}
|
||||
if !pattern_matched_propnames.is_empty() {
|
||||
output += OutputUnit::<Annotations<'_>>::annotations(
|
||||
self.pattern_keyword_path.clone(),
|
||||
instance_path.into(),
|
||||
self.pattern_keyword_absolute_path.clone(),
|
||||
serde_json::Value::from(pattern_matched_propnames).into(),
|
||||
)
|
||||
.into();
|
||||
}
|
||||
let mut result: PartialApplication = output.into();
|
||||
if !additional_matched_propnames.is_empty() {
|
||||
result.annotate(serde_json::Value::from(additional_matched_propnames).into())
|
||||
}
|
||||
result
|
||||
} else {
|
||||
PartialApplication::valid_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for AdditionalPropertiesWithPatternsValidator {
|
||||
|
@ -597,7 +719,7 @@ impl core::fmt::Display for AdditionalPropertiesWithPatternsValidator {
|
|||
write!(
|
||||
f,
|
||||
"additionalProperties: {}",
|
||||
format_validators(&self.validators)
|
||||
format_validators(self.node.validators())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -626,16 +748,23 @@ impl core::fmt::Display for AdditionalPropertiesWithPatternsValidator {
|
|||
pub(crate) struct AdditionalPropertiesWithPatternsFalseValidator {
|
||||
patterns: PatternedValidators,
|
||||
schema_path: JSONPointer,
|
||||
pattern_keyword_path: JSONPointer,
|
||||
pattern_keyword_absolute_path: Option<AbsolutePath>,
|
||||
}
|
||||
impl AdditionalPropertiesWithPatternsFalseValidator {
|
||||
#[inline]
|
||||
pub(crate) fn compile<'a>(
|
||||
patterns: PatternedValidators,
|
||||
schema_path: JSONPointer,
|
||||
context: &CompilationContext<'_>,
|
||||
) -> CompilationResult<'a> {
|
||||
Ok(Box::new(AdditionalPropertiesWithPatternsFalseValidator {
|
||||
patterns,
|
||||
schema_path,
|
||||
schema_path: context.as_pointer_with("additionalProperties"),
|
||||
pattern_keyword_path: context.as_pointer_with("patternProperties"),
|
||||
pattern_keyword_absolute_path: context
|
||||
.with_path("patternProperties")
|
||||
.base_uri()
|
||||
.map(|u| u.into()),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -665,9 +794,9 @@ impl Validate for AdditionalPropertiesWithPatternsFalseValidator {
|
|||
self.patterns
|
||||
.iter()
|
||||
.filter(|(re, _)| re.is_match(property).unwrap_or(false))
|
||||
.flat_map(|(_, validators)| {
|
||||
.flat_map(|(_, node)| {
|
||||
has_match = true;
|
||||
validate!(validators, schema, value, instance_path, property)
|
||||
validate!(node, schema, value, instance_path, property)
|
||||
}),
|
||||
);
|
||||
if !has_match {
|
||||
|
@ -687,6 +816,57 @@ impl Validate for AdditionalPropertiesWithPatternsFalseValidator {
|
|||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
if let Value::Object(item) = instance {
|
||||
let mut output = BasicOutput::default();
|
||||
let mut unexpected = Vec::with_capacity(item.len());
|
||||
let mut pattern_matched_props = Vec::with_capacity(item.len());
|
||||
for (property, value) in item {
|
||||
let path = instance_path.push(property.clone());
|
||||
let mut has_match = false;
|
||||
for (pattern, node) in &self.patterns {
|
||||
if pattern.is_match(property).unwrap_or(false) {
|
||||
has_match = true;
|
||||
pattern_matched_props.push(property.clone());
|
||||
output += node.apply_rooted(schema, value, &path);
|
||||
}
|
||||
}
|
||||
if !has_match {
|
||||
unexpected.push(property.clone());
|
||||
}
|
||||
}
|
||||
if !pattern_matched_props.is_empty() {
|
||||
output += OutputUnit::<Annotations<'_>>::annotations(
|
||||
self.pattern_keyword_path.clone(),
|
||||
instance_path.into(),
|
||||
self.pattern_keyword_absolute_path.clone(),
|
||||
serde_json::Value::from(pattern_matched_props).into(),
|
||||
)
|
||||
.into();
|
||||
}
|
||||
let mut result: PartialApplication = output.into();
|
||||
if !unexpected.is_empty() {
|
||||
result.mark_errored(
|
||||
ValidationError::additional_properties(
|
||||
self.schema_path.clone(),
|
||||
instance_path.into(),
|
||||
instance,
|
||||
unexpected,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
result
|
||||
} else {
|
||||
PartialApplication::valid_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for AdditionalPropertiesWithPatternsFalseValidator {
|
||||
|
@ -722,7 +902,7 @@ impl core::fmt::Display for AdditionalPropertiesWithPatternsFalseValidator {
|
|||
/// }
|
||||
/// ```
|
||||
pub(crate) struct AdditionalPropertiesWithPatternsNotEmptyValidator<M: PropertiesValidatorsMap> {
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
properties: M,
|
||||
patterns: PatternedValidators,
|
||||
}
|
||||
|
@ -737,7 +917,7 @@ impl AdditionalPropertiesWithPatternsNotEmptyValidator<SmallValidatorsMap> {
|
|||
let keyword_context = context.with_path("additionalProperties");
|
||||
Ok(Box::new(
|
||||
AdditionalPropertiesWithPatternsNotEmptyValidator {
|
||||
validators: compile_validators(schema, &keyword_context)?,
|
||||
node: compile_validators(schema, &keyword_context)?,
|
||||
properties: compile_small_map(map, context)?,
|
||||
patterns,
|
||||
},
|
||||
|
@ -755,7 +935,7 @@ impl AdditionalPropertiesWithPatternsNotEmptyValidator<BigValidatorsMap> {
|
|||
let keyword_context = context.with_path("additionalProperties");
|
||||
Ok(Box::new(
|
||||
AdditionalPropertiesWithPatternsNotEmptyValidator {
|
||||
validators: compile_validators(schema, &keyword_context)?,
|
||||
node: compile_validators(schema, &keyword_context)?,
|
||||
properties: compile_big_map(map, context)?,
|
||||
patterns,
|
||||
},
|
||||
|
@ -766,13 +946,13 @@ impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesWithPatternsNo
|
|||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
if let Value::Object(item) = instance {
|
||||
for (property, value) in item.iter() {
|
||||
if let Some(validators) = self.properties.get_validator(property) {
|
||||
if is_valid!(validators, schema, value) {
|
||||
if let Some(node) = self.properties.get_validator(property) {
|
||||
if is_valid!(node, schema, value) {
|
||||
// Valid for `properties`, check `patternProperties`
|
||||
for (re, validators) in &self.patterns {
|
||||
for (re, node) in &self.patterns {
|
||||
// If there is a match, then the value should match the sub-schema
|
||||
if re.is_match(property).unwrap_or(false) {
|
||||
is_valid_pattern_schema!(validators, schema, value)
|
||||
is_valid_pattern_schema!(node, schema, value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -781,14 +961,14 @@ impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesWithPatternsNo
|
|||
}
|
||||
} else {
|
||||
let mut has_match = false;
|
||||
for (re, validators) in &self.patterns {
|
||||
for (re, node) in &self.patterns {
|
||||
// If there is a match, then the value should match the sub-schema
|
||||
if re.is_match(property).unwrap_or(false) {
|
||||
has_match = true;
|
||||
is_valid_pattern_schema!(validators, schema, value)
|
||||
is_valid_pattern_schema!(node, schema, value)
|
||||
}
|
||||
}
|
||||
if !has_match && !is_valid!(self.validators, schema, value) {
|
||||
if !has_match && !is_valid!(self.node, schema, value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -808,8 +988,8 @@ impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesWithPatternsNo
|
|||
if let Value::Object(item) = instance {
|
||||
let mut errors = vec![];
|
||||
for (property, value) in item.iter() {
|
||||
if let Some((name, validators)) = self.properties.get_key_validator(property) {
|
||||
errors.extend(validate!(validators, schema, value, instance_path, name));
|
||||
if let Some((name, node)) = self.properties.get_key_validator(property) {
|
||||
errors.extend(validate!(node, schema, value, instance_path, name));
|
||||
errors.extend(
|
||||
self.patterns
|
||||
.iter()
|
||||
|
@ -824,19 +1004,13 @@ impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesWithPatternsNo
|
|||
self.patterns
|
||||
.iter()
|
||||
.filter(|(re, _)| re.is_match(property).unwrap_or(false))
|
||||
.flat_map(|(_, validators)| {
|
||||
.flat_map(|(_, node)| {
|
||||
has_match = true;
|
||||
validate!(validators, schema, value, instance_path, property)
|
||||
validate!(node, schema, value, instance_path, property)
|
||||
}),
|
||||
);
|
||||
if !has_match {
|
||||
errors.extend(validate!(
|
||||
self.validators,
|
||||
schema,
|
||||
value,
|
||||
instance_path,
|
||||
property
|
||||
))
|
||||
errors.extend(validate!(self.node, schema, value, instance_path, property))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -845,6 +1019,41 @@ impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesWithPatternsNo
|
|||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
if let Value::Object(item) = instance {
|
||||
let mut output = BasicOutput::default();
|
||||
let mut additional_matches = Vec::with_capacity(item.len());
|
||||
for (property, value) in item.iter() {
|
||||
let path = instance_path.push(property.clone());
|
||||
if let Some((_name, node)) = self.properties.get_key_validator(property) {
|
||||
output += node.apply_rooted(schema, value, &path);
|
||||
} else {
|
||||
let mut has_match = false;
|
||||
for (pattern, node) in &self.patterns {
|
||||
if pattern.is_match(property).unwrap_or(false) {
|
||||
has_match = true;
|
||||
output += node.apply_rooted(schema, value, &path);
|
||||
}
|
||||
}
|
||||
if !has_match {
|
||||
additional_matches.push(property.clone());
|
||||
output += self.node.apply_rooted(schema, value, &path);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut result: PartialApplication = output.into();
|
||||
result.annotate(serde_json::Value::from(additional_matches).into());
|
||||
result
|
||||
} else {
|
||||
PartialApplication::valid_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<M: PropertiesValidatorsMap> core::fmt::Display
|
||||
for AdditionalPropertiesWithPatternsNotEmptyValidator<M>
|
||||
|
@ -853,7 +1062,7 @@ impl<M: PropertiesValidatorsMap> core::fmt::Display
|
|||
write!(
|
||||
f,
|
||||
"additionalProperties: {}",
|
||||
format_validators(&self.validators)
|
||||
format_validators(self.node.validators())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -929,13 +1138,13 @@ impl<M: PropertiesValidatorsMap> Validate
|
|||
if let Value::Object(item) = instance {
|
||||
// No properties are allowed, except ones defined in `properties` or `patternProperties`
|
||||
for (property, value) in item.iter() {
|
||||
if let Some(validators) = self.properties.get_validator(property) {
|
||||
if is_valid!(validators, schema, value) {
|
||||
if let Some(node) = self.properties.get_validator(property) {
|
||||
if is_valid!(node, schema, value) {
|
||||
// Valid for `properties`, check `patternProperties`
|
||||
for (re, validators) in &self.patterns {
|
||||
for (re, node) in &self.patterns {
|
||||
// If there is a match, then the value should match the sub-schema
|
||||
if re.is_match(property).unwrap_or(false) {
|
||||
is_valid_pattern_schema!(validators, schema, value)
|
||||
is_valid_pattern_schema!(node, schema, value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -961,14 +1170,14 @@ impl<M: PropertiesValidatorsMap> Validate
|
|||
let mut unexpected = vec![];
|
||||
// No properties are allowed, except ones defined in `properties` or `patternProperties`
|
||||
for (property, value) in item.iter() {
|
||||
if let Some((name, validators)) = self.properties.get_key_validator(property) {
|
||||
errors.extend(validate!(validators, schema, value, instance_path, name));
|
||||
if let Some((name, node)) = self.properties.get_key_validator(property) {
|
||||
errors.extend(validate!(node, schema, value, instance_path, name));
|
||||
errors.extend(
|
||||
self.patterns
|
||||
.iter()
|
||||
.filter(|(re, _)| re.is_match(property).unwrap_or(false))
|
||||
.flat_map(|(_, validators)| {
|
||||
validate!(validators, schema, value, instance_path, name)
|
||||
.flat_map(|(_, node)| {
|
||||
validate!(node, schema, value, instance_path, name)
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
|
@ -977,9 +1186,9 @@ impl<M: PropertiesValidatorsMap> Validate
|
|||
self.patterns
|
||||
.iter()
|
||||
.filter(|(re, _)| re.is_match(property).unwrap_or(false))
|
||||
.flat_map(|(_, validators)| {
|
||||
.flat_map(|(_, node)| {
|
||||
has_match = true;
|
||||
validate!(validators, schema, value, instance_path, property)
|
||||
validate!(node, schema, value, instance_path, property)
|
||||
}),
|
||||
);
|
||||
if !has_match {
|
||||
|
@ -1000,6 +1209,51 @@ impl<M: PropertiesValidatorsMap> Validate
|
|||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
if let Value::Object(item) = instance {
|
||||
let mut output = BasicOutput::default();
|
||||
let mut unexpected = vec![];
|
||||
// No properties are allowed, except ones defined in `properties` or `patternProperties`
|
||||
for (property, value) in item.iter() {
|
||||
let path = instance_path.push(property.clone());
|
||||
if let Some((_name, node)) = self.properties.get_key_validator(property) {
|
||||
output += node.apply_rooted(schema, value, &path);
|
||||
} else {
|
||||
let mut has_match = false;
|
||||
for (pattern, node) in &self.patterns {
|
||||
if pattern.is_match(property).unwrap_or(false) {
|
||||
has_match = true;
|
||||
output += node.apply_rooted(schema, value, &path);
|
||||
}
|
||||
}
|
||||
if !has_match {
|
||||
unexpected.push(property.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut result: PartialApplication = output.into();
|
||||
if !unexpected.is_empty() {
|
||||
result.mark_errored(
|
||||
ValidationError::additional_properties(
|
||||
self.schema_path.clone(),
|
||||
instance_path.into(),
|
||||
instance,
|
||||
unexpected,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
result
|
||||
} else {
|
||||
PartialApplication::valid_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: PropertiesValidatorsMap> core::fmt::Display
|
||||
|
@ -1033,12 +1287,11 @@ pub(crate) fn compile<'a>(
|
|||
} else {
|
||||
Some(AdditionalPropertiesWithPatternsFalseValidator::compile(
|
||||
compiled_patterns,
|
||||
context.as_pointer_with("additionalProperties"),
|
||||
context,
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let keyword_context = context.with_path("additionalProperties");
|
||||
if let Some(properties) = properties {
|
||||
dynamic_map!(
|
||||
AdditionalPropertiesWithPatternsNotEmptyValidator,
|
||||
|
@ -1051,7 +1304,7 @@ pub(crate) fn compile<'a>(
|
|||
Some(AdditionalPropertiesWithPatternsValidator::compile(
|
||||
schema,
|
||||
compiled_patterns,
|
||||
&keyword_context,
|
||||
context,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -1104,8 +1357,8 @@ fn compile_patterns<'a>(
|
|||
for (pattern, subschema) in obj {
|
||||
let pattern_context = keyword_context.with_path(pattern.to_string());
|
||||
if let Ok(compiled_pattern) = Regex::new(pattern) {
|
||||
if let Ok(validators) = compile_validators(subschema, &pattern_context) {
|
||||
compiled_patterns.push((compiled_pattern, validators));
|
||||
if let Ok(node) = compile_validators(subschema, &pattern_context) {
|
||||
compiled_patterns.push((compiled_pattern, node));
|
||||
} else {
|
||||
return Err(ValidationError::schema(subschema));
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
use crate::{
|
||||
compilation::{compile_validators, context::CompilationContext, JSONSchema},
|
||||
error::{ErrorIterator, ValidationError},
|
||||
output::BasicOutput,
|
||||
paths::InstancePath,
|
||||
validator::{format_validators, format_vec_of_validators, Validate, Validators},
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_iter_of_validators, format_validators, PartialApplication, Validate},
|
||||
};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use super::CompilationResult;
|
||||
|
||||
pub(crate) struct AllOfValidator {
|
||||
schemas: Vec<Validators>,
|
||||
schemas: Vec<SchemaNode>,
|
||||
}
|
||||
|
||||
impl AllOfValidator {
|
||||
|
@ -31,13 +33,10 @@ impl AllOfValidator {
|
|||
|
||||
impl Validate for AllOfValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
self.schemas.iter().all(move |validators| {
|
||||
validators
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, instance))
|
||||
})
|
||||
self.schemas.iter().all(|n| n.is_valid(schema, instance))
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
|
@ -47,23 +46,36 @@ impl Validate for AllOfValidator {
|
|||
let errors: Vec<_> = self
|
||||
.schemas
|
||||
.iter()
|
||||
.flat_map(move |validators| {
|
||||
validators
|
||||
.iter()
|
||||
.flat_map(move |validator| validator.validate(schema, instance, instance_path))
|
||||
})
|
||||
.flat_map(move |node| node.validate(schema, instance, instance_path))
|
||||
.collect();
|
||||
Box::new(errors.into_iter())
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
self.schemas
|
||||
.iter()
|
||||
.map(move |node| node.apply_rooted(schema, instance, instance_path))
|
||||
.sum::<BasicOutput<'_>>()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for AllOfValidator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "allOf: [{}]", format_vec_of_validators(&self.schemas))
|
||||
write!(
|
||||
f,
|
||||
"allOf: [{}]",
|
||||
format_iter_of_validators(self.schemas.iter().map(|s| s.validators()))
|
||||
)
|
||||
}
|
||||
}
|
||||
pub(crate) struct SingleValueAllOfValidator {
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
}
|
||||
|
||||
impl SingleValueAllOfValidator {
|
||||
|
@ -74,16 +86,14 @@ impl SingleValueAllOfValidator {
|
|||
) -> CompilationResult<'a> {
|
||||
let keyword_context = context.with_path("allOf");
|
||||
let item_context = keyword_context.with_path(0);
|
||||
let validators = compile_validators(schema, &item_context)?;
|
||||
Ok(Box::new(SingleValueAllOfValidator { validators }))
|
||||
let node = compile_validators(schema, &item_context)?;
|
||||
Ok(Box::new(SingleValueAllOfValidator { node }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Validate for SingleValueAllOfValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
self.validators
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, instance))
|
||||
self.node.is_valid(schema, instance)
|
||||
}
|
||||
|
||||
fn validate<'a, 'b>(
|
||||
|
@ -92,18 +102,24 @@ impl Validate for SingleValueAllOfValidator {
|
|||
instance: &'b Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> ErrorIterator<'b> {
|
||||
let errors: Vec<_> = self
|
||||
.validators
|
||||
.iter()
|
||||
.flat_map(move |validator| validator.validate(schema, instance, instance_path))
|
||||
.collect();
|
||||
Box::new(errors.into_iter())
|
||||
self.node.validate(schema, instance, instance_path)
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
self.node
|
||||
.apply_rooted(schema, instance, instance_path)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for SingleValueAllOfValidator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "allOf: [{}]", format_validators(&self.validators))
|
||||
write!(f, "allOf: [{}]", format_validators(self.node.validators()))
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
|
|
|
@ -2,7 +2,8 @@ use crate::{
|
|||
compilation::{compile_validators, context::CompilationContext, JSONSchema},
|
||||
error::{error, no_error, ErrorIterator, ValidationError},
|
||||
paths::InstancePath,
|
||||
validator::{format_vec_of_validators, Validate, Validators},
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_iter_of_validators, PartialApplication, Validate},
|
||||
};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
|
@ -10,7 +11,7 @@ use super::CompilationResult;
|
|||
use crate::paths::JSONPointer;
|
||||
|
||||
pub(crate) struct AnyOfValidator {
|
||||
schemas: Vec<Validators>,
|
||||
schemas: Vec<SchemaNode>,
|
||||
schema_path: JSONPointer,
|
||||
}
|
||||
|
||||
|
@ -25,8 +26,8 @@ impl AnyOfValidator {
|
|||
let mut schemas = Vec::with_capacity(items.len());
|
||||
for (idx, item) in items.iter().enumerate() {
|
||||
let item_context = keyword_context.with_path(idx);
|
||||
let validators = compile_validators(item, &item_context)?;
|
||||
schemas.push(validators)
|
||||
let node = compile_validators(item, &item_context)?;
|
||||
schemas.push(node)
|
||||
}
|
||||
Ok(Box::new(AnyOfValidator {
|
||||
schemas,
|
||||
|
@ -40,15 +41,7 @@ impl AnyOfValidator {
|
|||
|
||||
impl Validate for AnyOfValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
for validators in &self.schemas {
|
||||
if validators
|
||||
.iter()
|
||||
.all(|validator| validator.is_valid(schema, instance))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
self.schemas.iter().any(|s| s.is_valid(schema, instance))
|
||||
}
|
||||
|
||||
fn validate<'a, 'b>(
|
||||
|
@ -67,11 +60,38 @@ impl Validate for AnyOfValidator {
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
let mut successes = Vec::new();
|
||||
let mut failures = Vec::new();
|
||||
for node in &self.schemas {
|
||||
let result = node.apply_rooted(schema, instance, instance_path);
|
||||
if result.is_valid() {
|
||||
successes.push(result);
|
||||
} else {
|
||||
failures.push(result);
|
||||
}
|
||||
}
|
||||
if successes.is_empty() {
|
||||
failures.into_iter().collect()
|
||||
} else {
|
||||
successes.into_iter().collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for AnyOfValidator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "anyOf: [{}]", format_vec_of_validators(&self.schemas))
|
||||
write!(
|
||||
f,
|
||||
"anyOf: [{}]",
|
||||
format_iter_of_validators(self.schemas.iter().map(|s| s.validators()))
|
||||
)
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
|
|
|
@ -3,13 +3,14 @@ use crate::{
|
|||
error::{error, no_error, ErrorIterator, ValidationError},
|
||||
keywords::CompilationResult,
|
||||
paths::{InstancePath, JSONPointer},
|
||||
validator::{format_validators, Validate, Validators},
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_validators, PartialApplication, Validate},
|
||||
Draft,
|
||||
};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub(crate) struct ContainsValidator {
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
schema_path: JSONPointer,
|
||||
}
|
||||
|
||||
|
@ -21,7 +22,7 @@ impl ContainsValidator {
|
|||
) -> CompilationResult<'a> {
|
||||
let keyword_context = context.with_path("contains");
|
||||
Ok(Box::new(ContainsValidator {
|
||||
validators: compile_validators(schema, &keyword_context)?,
|
||||
node: compile_validators(schema, &keyword_context)?,
|
||||
schema_path: keyword_context.into_pointer(),
|
||||
}))
|
||||
}
|
||||
|
@ -30,16 +31,7 @@ impl ContainsValidator {
|
|||
impl Validate for ContainsValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
if let Value::Array(items) = instance {
|
||||
for item in items {
|
||||
if self
|
||||
.validators
|
||||
.iter()
|
||||
.all(|validator| validator.is_valid(schema, item))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
items.iter().any(|i| self.node.is_valid(schema, i))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
@ -52,14 +44,8 @@ impl Validate for ContainsValidator {
|
|||
instance_path: &InstancePath,
|
||||
) -> ErrorIterator<'b> {
|
||||
if let Value::Array(items) = instance {
|
||||
for item in items {
|
||||
if self
|
||||
.validators
|
||||
.iter()
|
||||
.all(|validator| validator.is_valid(schema, item))
|
||||
{
|
||||
return no_error();
|
||||
}
|
||||
if items.iter().any(|i| self.node.is_valid(schema, i)) {
|
||||
return no_error();
|
||||
}
|
||||
error(ValidationError::contains(
|
||||
self.schema_path.clone(),
|
||||
|
@ -70,11 +56,49 @@ impl Validate for ContainsValidator {
|
|||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
if let Value::Array(items) = instance {
|
||||
let mut results = Vec::with_capacity(items.len());
|
||||
let mut indices = Vec::new();
|
||||
for (idx, item) in items.iter().enumerate() {
|
||||
let path = instance_path.push(idx);
|
||||
let result = self.node.apply_rooted(schema, item, &path);
|
||||
if result.is_valid() {
|
||||
indices.push(idx);
|
||||
results.push(result);
|
||||
}
|
||||
}
|
||||
let mut result: PartialApplication = results.into_iter().collect();
|
||||
if indices.is_empty() {
|
||||
result.mark_errored(
|
||||
ValidationError::contains(
|
||||
self.schema_path.clone(),
|
||||
instance_path.into(),
|
||||
instance,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
} else {
|
||||
result.annotate(serde_json::Value::from(indices).into());
|
||||
}
|
||||
result
|
||||
} else {
|
||||
let mut result = PartialApplication::valid_empty();
|
||||
result.annotate(serde_json::Value::Array(Vec::new()).into());
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for ContainsValidator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "contains: {}", format_validators(&self.validators))
|
||||
write!(f, "contains: {}", format_validators(self.node.validators()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +106,7 @@ impl core::fmt::Display for ContainsValidator {
|
|||
///
|
||||
/// Docs: <https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.4.5>
|
||||
pub(crate) struct MinContainsValidator {
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
min_contains: u64,
|
||||
schema_path: JSONPointer,
|
||||
}
|
||||
|
@ -96,7 +120,7 @@ impl MinContainsValidator {
|
|||
) -> CompilationResult<'a> {
|
||||
let keyword_context = context.with_path("minContains");
|
||||
Ok(Box::new(MinContainsValidator {
|
||||
validators: compile_validators(schema, context)?,
|
||||
node: compile_validators(schema, context)?,
|
||||
min_contains,
|
||||
schema_path: keyword_context.into_pointer(),
|
||||
}))
|
||||
|
@ -118,8 +142,8 @@ impl Validate for MinContainsValidator {
|
|||
let mut matches = 0;
|
||||
for item in items {
|
||||
if self
|
||||
.validators
|
||||
.iter()
|
||||
.node
|
||||
.validators()
|
||||
.all(|validator| validator.is_valid(schema, item))
|
||||
{
|
||||
matches += 1;
|
||||
|
@ -149,8 +173,8 @@ impl Validate for MinContainsValidator {
|
|||
let mut matches = 0;
|
||||
for item in items {
|
||||
if self
|
||||
.validators
|
||||
.iter()
|
||||
.node
|
||||
.validators()
|
||||
.all(|validator| validator.is_valid(schema, item))
|
||||
{
|
||||
matches += 1;
|
||||
|
@ -172,7 +196,7 @@ impl core::fmt::Display for MinContainsValidator {
|
|||
f,
|
||||
"minContains: {}, contains: {}",
|
||||
self.min_contains,
|
||||
format_validators(&self.validators)
|
||||
format_validators(self.node.validators())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -181,7 +205,7 @@ impl core::fmt::Display for MinContainsValidator {
|
|||
///
|
||||
/// Docs: <https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.4.4>
|
||||
pub(crate) struct MaxContainsValidator {
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
max_contains: u64,
|
||||
schema_path: JSONPointer,
|
||||
}
|
||||
|
@ -195,7 +219,7 @@ impl MaxContainsValidator {
|
|||
) -> CompilationResult<'a> {
|
||||
let keyword_context = context.with_path("maxContains");
|
||||
Ok(Box::new(MaxContainsValidator {
|
||||
validators: compile_validators(schema, context)?,
|
||||
node: compile_validators(schema, context)?,
|
||||
max_contains,
|
||||
schema_path: keyword_context.into_pointer(),
|
||||
}))
|
||||
|
@ -217,8 +241,8 @@ impl Validate for MaxContainsValidator {
|
|||
let mut matches = 0;
|
||||
for item in items {
|
||||
if self
|
||||
.validators
|
||||
.iter()
|
||||
.node
|
||||
.validators()
|
||||
.all(|validator| validator.is_valid(schema, item))
|
||||
{
|
||||
matches += 1;
|
||||
|
@ -254,8 +278,8 @@ impl Validate for MaxContainsValidator {
|
|||
let mut matches = 0;
|
||||
for item in items {
|
||||
if self
|
||||
.validators
|
||||
.iter()
|
||||
.node
|
||||
.validators()
|
||||
.all(|validator| validator.is_valid(schema, item))
|
||||
{
|
||||
matches += 1;
|
||||
|
@ -277,7 +301,7 @@ impl core::fmt::Display for MaxContainsValidator {
|
|||
f,
|
||||
"maxContains: {}, contains: {}",
|
||||
self.max_contains,
|
||||
format_validators(&self.validators)
|
||||
format_validators(self.node.validators())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -288,7 +312,7 @@ impl core::fmt::Display for MaxContainsValidator {
|
|||
/// `maxContains` - <https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.4.4>
|
||||
/// `minContains` - <https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.4.5>
|
||||
pub(crate) struct MinMaxContainsValidator {
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
min_contains: u64,
|
||||
max_contains: u64,
|
||||
schema_path: JSONPointer,
|
||||
|
@ -303,7 +327,7 @@ impl MinMaxContainsValidator {
|
|||
max_contains: u64,
|
||||
) -> CompilationResult<'a> {
|
||||
Ok(Box::new(MinMaxContainsValidator {
|
||||
validators: compile_validators(schema, context)?,
|
||||
node: compile_validators(schema, context)?,
|
||||
min_contains,
|
||||
max_contains,
|
||||
schema_path: context.schema_path.clone().into(),
|
||||
|
@ -322,8 +346,8 @@ impl Validate for MinMaxContainsValidator {
|
|||
let mut matches = 0;
|
||||
for item in items {
|
||||
if self
|
||||
.validators
|
||||
.iter()
|
||||
.node
|
||||
.validators()
|
||||
.all(|validator| validator.is_valid(schema, item))
|
||||
{
|
||||
matches += 1;
|
||||
|
@ -357,8 +381,8 @@ impl Validate for MinMaxContainsValidator {
|
|||
let mut matches = 0;
|
||||
for item in items {
|
||||
if self
|
||||
.validators
|
||||
.iter()
|
||||
.node
|
||||
.validators()
|
||||
.all(|validator| validator.is_valid(schema, item))
|
||||
{
|
||||
matches += 1;
|
||||
|
@ -381,7 +405,7 @@ impl core::fmt::Display for MinMaxContainsValidator {
|
|||
"minContains: {}, maxContains: {}, contains: {}",
|
||||
self.min_contains,
|
||||
self.max_contains,
|
||||
format_validators(&self.validators)
|
||||
format_validators(self.node.validators())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,13 @@ use crate::{
|
|||
error::{no_error, ErrorIterator, ValidationError},
|
||||
keywords::{required, CompilationResult},
|
||||
paths::InstancePath,
|
||||
validator::{format_key_value_validators, Validate, Validators},
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_key_value_validators, Validate},
|
||||
};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub(crate) struct DependenciesValidator {
|
||||
dependencies: Vec<(String, Validators)>,
|
||||
dependencies: Vec<(String, SchemaNode)>,
|
||||
}
|
||||
|
||||
impl DependenciesValidator {
|
||||
|
@ -24,11 +25,12 @@ impl DependenciesValidator {
|
|||
let item_context = keyword_context.with_path(key.to_string());
|
||||
let s = match subschema {
|
||||
Value::Array(_) => {
|
||||
vec![required::compile_with_path(
|
||||
let validators = vec![required::compile_with_path(
|
||||
subschema,
|
||||
(&keyword_context.schema_path).into(),
|
||||
)
|
||||
.expect("The required validator compilation does not return None")?]
|
||||
.expect("The required validator compilation does not return None")?];
|
||||
SchemaNode::new_from_array(&keyword_context, validators)
|
||||
}
|
||||
_ => compile_validators(subschema, &item_context)?,
|
||||
};
|
||||
|
@ -47,16 +49,13 @@ impl Validate for DependenciesValidator {
|
|||
self.dependencies
|
||||
.iter()
|
||||
.filter(|(property, _)| item.contains_key(property))
|
||||
.all(move |(_, validators)| {
|
||||
validators
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, instance))
|
||||
})
|
||||
.all(move |(_, node)| node.is_valid(schema, instance))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
|
@ -68,11 +67,7 @@ impl Validate for DependenciesValidator {
|
|||
.dependencies
|
||||
.iter()
|
||||
.filter(|(property, _)| item.contains_key(property))
|
||||
.flat_map(move |(_, validators)| {
|
||||
validators.iter().flat_map(move |validator| {
|
||||
validator.validate(schema, instance, instance_path)
|
||||
})
|
||||
})
|
||||
.flat_map(move |(_, node)| node.validate(schema, instance, instance_path))
|
||||
.collect();
|
||||
// TODO. custom error message for "required" case
|
||||
Box::new(errors.into_iter())
|
||||
|
|
|
@ -3,13 +3,14 @@ use crate::{
|
|||
error::{no_error, ErrorIterator},
|
||||
keywords::CompilationResult,
|
||||
paths::InstancePath,
|
||||
validator::{format_validators, Validate, Validators},
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_validators, PartialApplication, Validate},
|
||||
};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub(crate) struct IfThenValidator {
|
||||
schema: Validators,
|
||||
then_schema: Validators,
|
||||
schema: SchemaNode,
|
||||
then_schema: SchemaNode,
|
||||
}
|
||||
|
||||
impl IfThenValidator {
|
||||
|
@ -34,40 +35,46 @@ impl IfThenValidator {
|
|||
|
||||
impl Validate for IfThenValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
if self
|
||||
.schema
|
||||
.iter()
|
||||
.all(|validator| validator.is_valid(schema, instance))
|
||||
{
|
||||
self.then_schema
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, instance))
|
||||
if self.schema.is_valid(schema, instance) {
|
||||
self.then_schema.is_valid(schema, instance)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
instance: &'b Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> ErrorIterator<'b> {
|
||||
if self
|
||||
.schema
|
||||
.iter()
|
||||
.all(|validator| validator.is_valid(schema, instance))
|
||||
{
|
||||
if self.schema.is_valid(schema, instance) {
|
||||
let errors: Vec<_> = self
|
||||
.then_schema
|
||||
.iter()
|
||||
.flat_map(move |validator| validator.validate(schema, instance, instance_path))
|
||||
.validate(schema, instance, instance_path)
|
||||
.collect();
|
||||
Box::new(errors.into_iter())
|
||||
} else {
|
||||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
let mut if_result = self.schema.apply_rooted(schema, instance, instance_path);
|
||||
if if_result.is_valid() {
|
||||
let then_result = self
|
||||
.then_schema
|
||||
.apply_rooted(schema, instance, instance_path);
|
||||
if_result += then_result;
|
||||
}
|
||||
if_result.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for IfThenValidator {
|
||||
|
@ -75,15 +82,15 @@ impl core::fmt::Display for IfThenValidator {
|
|||
write!(
|
||||
f,
|
||||
"if: {}, then: {}",
|
||||
format_validators(&self.schema),
|
||||
format_validators(&self.then_schema)
|
||||
format_validators(self.schema.validators()),
|
||||
format_validators(self.then_schema.validators())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct IfElseValidator {
|
||||
schema: Validators,
|
||||
else_schema: Validators,
|
||||
schema: SchemaNode,
|
||||
else_schema: SchemaNode,
|
||||
}
|
||||
|
||||
impl IfElseValidator {
|
||||
|
@ -108,40 +115,46 @@ impl IfElseValidator {
|
|||
|
||||
impl Validate for IfElseValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
if self
|
||||
.schema
|
||||
.iter()
|
||||
.any(|validator| !validator.is_valid(schema, instance))
|
||||
{
|
||||
self.else_schema
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, instance))
|
||||
if !self.schema.is_valid(schema, instance) {
|
||||
self.else_schema.is_valid(schema, instance)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
instance: &'b Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> ErrorIterator<'b> {
|
||||
if self
|
||||
.schema
|
||||
.iter()
|
||||
.any(|validator| !validator.is_valid(schema, instance))
|
||||
{
|
||||
if !self.schema.is_valid(schema, instance) {
|
||||
let errors: Vec<_> = self
|
||||
.else_schema
|
||||
.iter()
|
||||
.flat_map(move |validator| validator.validate(schema, instance, instance_path))
|
||||
.validate(schema, instance, instance_path)
|
||||
.collect();
|
||||
Box::new(errors.into_iter())
|
||||
} else {
|
||||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
let if_result = self.schema.apply_rooted(schema, instance, instance_path);
|
||||
if if_result.is_valid() {
|
||||
if_result.into()
|
||||
} else {
|
||||
self.else_schema
|
||||
.apply_rooted(schema, instance, instance_path)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for IfElseValidator {
|
||||
|
@ -149,16 +162,16 @@ impl core::fmt::Display for IfElseValidator {
|
|||
write!(
|
||||
f,
|
||||
"if: {}, else: {}",
|
||||
format_validators(&self.schema),
|
||||
format_validators(&self.else_schema)
|
||||
format_validators(self.schema.validators()),
|
||||
format_validators(self.else_schema.validators())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct IfThenElseValidator {
|
||||
schema: Validators,
|
||||
then_schema: Validators,
|
||||
else_schema: Validators,
|
||||
schema: SchemaNode,
|
||||
then_schema: SchemaNode,
|
||||
else_schema: SchemaNode,
|
||||
}
|
||||
|
||||
impl IfThenElseValidator {
|
||||
|
@ -188,47 +201,53 @@ impl IfThenElseValidator {
|
|||
|
||||
impl Validate for IfThenElseValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
if self
|
||||
.schema
|
||||
.iter()
|
||||
.all(|validator| validator.is_valid(schema, instance))
|
||||
{
|
||||
self.then_schema
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, instance))
|
||||
if self.schema.is_valid(schema, instance) {
|
||||
self.then_schema.is_valid(schema, instance)
|
||||
} else {
|
||||
self.else_schema
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, instance))
|
||||
self.else_schema.is_valid(schema, instance)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
instance: &'b Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> ErrorIterator<'b> {
|
||||
if self
|
||||
.schema
|
||||
.iter()
|
||||
.all(|validator| validator.is_valid(schema, instance))
|
||||
{
|
||||
if self.schema.is_valid(schema, instance) {
|
||||
let errors: Vec<_> = self
|
||||
.then_schema
|
||||
.iter()
|
||||
.flat_map(move |validator| validator.validate(schema, instance, instance_path))
|
||||
.validate(schema, instance, instance_path)
|
||||
.collect();
|
||||
Box::new(errors.into_iter())
|
||||
} else {
|
||||
let errors: Vec<_> = self
|
||||
.else_schema
|
||||
.iter()
|
||||
.flat_map(move |validator| validator.validate(schema, instance, instance_path))
|
||||
.validate(schema, instance, instance_path)
|
||||
.collect();
|
||||
Box::new(errors.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
let mut if_result = self.schema.apply_rooted(schema, instance, instance_path);
|
||||
if if_result.is_valid() {
|
||||
if_result += self
|
||||
.then_schema
|
||||
.apply_rooted(schema, instance, instance_path);
|
||||
if_result.into()
|
||||
} else {
|
||||
self.else_schema
|
||||
.apply_rooted(schema, instance, instance_path)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for IfThenElseValidator {
|
||||
|
@ -236,9 +255,9 @@ impl core::fmt::Display for IfThenElseValidator {
|
|||
write!(
|
||||
f,
|
||||
"if: {}, then: {}, else: {}",
|
||||
format_validators(&self.schema),
|
||||
format_validators(&self.then_schema),
|
||||
format_validators(&self.else_schema)
|
||||
format_validators(self.schema.validators()),
|
||||
format_validators(self.then_schema.validators()),
|
||||
format_validators(self.else_schema.validators())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,13 @@ use crate::{
|
|||
error::{no_error, ErrorIterator},
|
||||
keywords::CompilationResult,
|
||||
paths::InstancePath,
|
||||
validator::{format_validators, format_vec_of_validators, Validate, Validators},
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_iter_of_validators, format_validators, PartialApplication, Validate},
|
||||
};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub(crate) struct ItemsArrayValidator {
|
||||
items: Vec<Validators>,
|
||||
items: Vec<SchemaNode>,
|
||||
}
|
||||
impl ItemsArrayValidator {
|
||||
#[inline]
|
||||
|
@ -32,16 +33,13 @@ impl Validate for ItemsArrayValidator {
|
|||
items
|
||||
.iter()
|
||||
.zip(self.items.iter())
|
||||
.all(move |(item, validators)| {
|
||||
validators
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, item))
|
||||
})
|
||||
.all(move |(item, node)| node.is_valid(schema, item))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
|
@ -53,10 +51,8 @@ impl Validate for ItemsArrayValidator {
|
|||
.iter()
|
||||
.zip(self.items.iter())
|
||||
.enumerate()
|
||||
.flat_map(move |(idx, (item, validators))| {
|
||||
validators.iter().flat_map(move |validator| {
|
||||
validator.validate(schema, item, &instance_path.push(idx))
|
||||
})
|
||||
.flat_map(move |(idx, (item, node))| {
|
||||
node.validate(schema, item, &instance_path.push(idx))
|
||||
})
|
||||
.collect();
|
||||
Box::new(errors.into_iter())
|
||||
|
@ -68,12 +64,16 @@ impl Validate for ItemsArrayValidator {
|
|||
|
||||
impl core::fmt::Display for ItemsArrayValidator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "items: [{}]", format_vec_of_validators(&self.items))
|
||||
write!(
|
||||
f,
|
||||
"items: [{}]",
|
||||
format_iter_of_validators(self.items.iter().map(|i| i.validators()))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ItemsObjectValidator {
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
}
|
||||
impl ItemsObjectValidator {
|
||||
#[inline]
|
||||
|
@ -82,23 +82,20 @@ impl ItemsObjectValidator {
|
|||
context: &CompilationContext,
|
||||
) -> CompilationResult<'a> {
|
||||
let keyword_context = context.with_path("items");
|
||||
let validators = compile_validators(schema, &keyword_context)?;
|
||||
Ok(Box::new(ItemsObjectValidator { validators }))
|
||||
let node = compile_validators(schema, &keyword_context)?;
|
||||
Ok(Box::new(ItemsObjectValidator { node }))
|
||||
}
|
||||
}
|
||||
impl Validate for ItemsObjectValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
if let Value::Array(items) = instance {
|
||||
self.validators.iter().all(move |validator| {
|
||||
items
|
||||
.iter()
|
||||
.all(move |item| validator.is_valid(schema, item))
|
||||
})
|
||||
items.iter().all(|i| self.node.is_valid(schema, i))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
|
@ -106,13 +103,11 @@ impl Validate for ItemsObjectValidator {
|
|||
instance_path: &InstancePath,
|
||||
) -> ErrorIterator<'b> {
|
||||
if let Value::Array(items) = instance {
|
||||
let errors: Vec<_> = self
|
||||
.validators
|
||||
let errors: Vec<_> = items
|
||||
.iter()
|
||||
.flat_map(move |validator| {
|
||||
items.iter().enumerate().flat_map(move |(idx, item)| {
|
||||
validator.validate(schema, item, &instance_path.push(idx))
|
||||
})
|
||||
.enumerate()
|
||||
.flat_map(move |(idx, item)| {
|
||||
self.node.validate(schema, item, &instance_path.push(idx))
|
||||
})
|
||||
.collect();
|
||||
Box::new(errors.into_iter())
|
||||
|
@ -120,11 +115,36 @@ impl Validate for ItemsObjectValidator {
|
|||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
if let Value::Array(items) = instance {
|
||||
let mut results = Vec::with_capacity(items.len());
|
||||
for (idx, item) in items.iter().enumerate() {
|
||||
let path = instance_path.push(idx);
|
||||
results.push(self.node.apply_rooted(schema, item, &path));
|
||||
}
|
||||
let mut output: PartialApplication = results.into_iter().collect();
|
||||
// Per draft 2020-12 section https://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.10.3.1.2
|
||||
// we must produce an annotation with a boolean value indicating whether the subschema
|
||||
// was applied to any positions in the underlying array. As we have not yet implemented
|
||||
// prefixItems this is true if there are any items in the instance.
|
||||
let schema_was_applied = !items.is_empty();
|
||||
output.annotate(serde_json::json! {schema_was_applied}.into());
|
||||
output
|
||||
} else {
|
||||
PartialApplication::valid_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for ItemsObjectValidator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "items: {}", format_validators(&self.validators))
|
||||
write!(f, "items: {}", format_validators(self.node.validators()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -122,7 +122,10 @@ mod tests {
|
|||
#[test_case(&json!({"uniqueItems": true}), "uniqueItems: true")]
|
||||
fn debug_representation(schema: &Value, expected: &str) {
|
||||
let compiled = JSONSchema::compile(schema).unwrap();
|
||||
assert_eq!(format!("{:?}", compiled.validators[0]), expected);
|
||||
assert_eq!(
|
||||
format!("{:?}", compiled.node.validators().next().unwrap()),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(&json!({"items": [{}], "additionalItems": {"type": "integer"}}), &json!([ null, 2, 3, "foo" ]), r#""foo" is not of type "integer""#)]
|
||||
|
|
|
@ -3,14 +3,15 @@ use crate::{
|
|||
error::{error, no_error, ErrorIterator, ValidationError},
|
||||
keywords::CompilationResult,
|
||||
paths::{InstancePath, JSONPointer},
|
||||
validator::{format_validators, Validate, Validators},
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_validators, Validate},
|
||||
};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub(crate) struct NotValidator {
|
||||
// needed only for error representation
|
||||
original: Value,
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
schema_path: JSONPointer,
|
||||
}
|
||||
|
||||
|
@ -23,7 +24,7 @@ impl NotValidator {
|
|||
let keyword_context = context.with_path("not");
|
||||
Ok(Box::new(NotValidator {
|
||||
original: schema.clone(),
|
||||
validators: compile_validators(schema, &keyword_context)?,
|
||||
node: compile_validators(schema, &keyword_context)?,
|
||||
schema_path: keyword_context.into_pointer(),
|
||||
}))
|
||||
}
|
||||
|
@ -31,10 +32,7 @@ impl NotValidator {
|
|||
|
||||
impl Validate for NotValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
!self
|
||||
.validators
|
||||
.iter()
|
||||
.all(|validator| validator.is_valid(schema, instance))
|
||||
!self.node.is_valid(schema, instance)
|
||||
}
|
||||
|
||||
fn validate<'a, 'b>(
|
||||
|
@ -58,7 +56,7 @@ impl Validate for NotValidator {
|
|||
|
||||
impl core::fmt::Display for NotValidator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "not: {}", format_validators(&self.validators))
|
||||
write!(f, "not: {}", format_validators(self.node.validators()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,13 +2,15 @@ use crate::{
|
|||
compilation::{compile_validators, context::CompilationContext, JSONSchema},
|
||||
error::{error, no_error, ErrorIterator, ValidationError},
|
||||
keywords::CompilationResult,
|
||||
output::BasicOutput,
|
||||
paths::{InstancePath, JSONPointer},
|
||||
validator::{format_vec_of_validators, Validate, Validators},
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_iter_of_validators, PartialApplication, Validate},
|
||||
};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub(crate) struct OneOfValidator {
|
||||
schemas: Vec<Validators>,
|
||||
schemas: Vec<SchemaNode>,
|
||||
schema_path: JSONPointer,
|
||||
}
|
||||
|
||||
|
@ -21,8 +23,10 @@ impl OneOfValidator {
|
|||
if let Value::Array(items) = schema {
|
||||
let keyword_context = context.with_path("oneOf");
|
||||
let mut schemas = Vec::with_capacity(items.len());
|
||||
for item in items {
|
||||
schemas.push(compile_validators(item, &keyword_context)?)
|
||||
for (idx, item) in items.iter().enumerate() {
|
||||
let item_context = keyword_context.with_path(idx);
|
||||
let node = compile_validators(item, &item_context)?;
|
||||
schemas.push(node)
|
||||
}
|
||||
Ok(Box::new(OneOfValidator {
|
||||
schemas,
|
||||
|
@ -35,11 +39,8 @@ impl OneOfValidator {
|
|||
|
||||
fn get_first_valid(&self, schema: &JSONSchema, instance: &Value) -> Option<usize> {
|
||||
let mut first_valid_idx = None;
|
||||
for (idx, validators) in self.schemas.iter().enumerate() {
|
||||
if validators
|
||||
.iter()
|
||||
.all(|validator| validator.is_valid(schema, instance))
|
||||
{
|
||||
for (idx, node) in self.schemas.iter().enumerate() {
|
||||
if node.is_valid(schema, instance) {
|
||||
first_valid_idx = Some(idx);
|
||||
break;
|
||||
}
|
||||
|
@ -52,15 +53,10 @@ impl OneOfValidator {
|
|||
// `idx + 1` will not overflow, because the maximum possible value there is `usize::MAX - 1`
|
||||
// For example we have `usize::MAX` schemas and only the last one is valid, then
|
||||
// in `get_first_valid` we enumerate from `0`, and on the last index will be `usize::MAX - 1`
|
||||
for validators in self.schemas.iter().skip(idx + 1) {
|
||||
if validators
|
||||
.iter()
|
||||
.all(|validator| validator.is_valid(schema, instance))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
self.schemas
|
||||
.iter()
|
||||
.skip(idx + 1)
|
||||
.any(|n| n.is_valid(schema, instance))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,11 +89,40 @@ impl Validate for OneOfValidator {
|
|||
))
|
||||
}
|
||||
}
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
let mut failures = Vec::new();
|
||||
let mut successes = Vec::new();
|
||||
for node in &self.schemas {
|
||||
match node.apply_rooted(schema, instance, instance_path) {
|
||||
output @ BasicOutput::Valid(..) => successes.push(output),
|
||||
output @ BasicOutput::Invalid(..) => failures.push(output),
|
||||
};
|
||||
}
|
||||
if successes.len() == 1 {
|
||||
let success = successes.remove(0);
|
||||
success.into()
|
||||
} else if successes.len() > 1 {
|
||||
PartialApplication::invalid_empty(vec!["more than one subschema succeeded".into()])
|
||||
} else if !failures.is_empty() {
|
||||
failures.into_iter().sum::<BasicOutput<'_>>().into()
|
||||
} else {
|
||||
unreachable!("compilation should fail for oneOf with no subschemas")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for OneOfValidator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "oneOf: [{}]", format_vec_of_validators(&self.schemas))
|
||||
write!(
|
||||
f,
|
||||
"oneOf: [{}]",
|
||||
format_iter_of_validators(self.schemas.iter().map(|s| s.validators()))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -159,7 +159,7 @@ pub(crate) fn compile<'a>(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{compilation::DEFAULT_SCOPE, tests_util};
|
||||
use crate::{compilation::context::BaseUri, tests_util};
|
||||
use serde_json::{json, Value};
|
||||
use test_case::test_case;
|
||||
|
||||
|
@ -188,7 +188,7 @@ mod tests {
|
|||
let text = Value::String(text.into());
|
||||
let schema = json!({});
|
||||
let schema = JSONSchema::compile(&schema).unwrap();
|
||||
let context = CompilationContext::new(DEFAULT_SCOPE.clone(), schema.config());
|
||||
let context = CompilationContext::new(BaseUri::Unknown, schema.config());
|
||||
let compiled = PatternValidator::compile(&pattern, &context).unwrap();
|
||||
assert_eq!(compiled.is_valid(&schema, &text), is_matching,)
|
||||
}
|
||||
|
|
|
@ -2,14 +2,16 @@ use crate::{
|
|||
compilation::{compile_validators, context::CompilationContext, JSONSchema},
|
||||
error::{no_error, ErrorIterator, ValidationError},
|
||||
keywords::CompilationResult,
|
||||
output::BasicOutput,
|
||||
paths::InstancePath,
|
||||
validator::{format_validators, Validate, Validators},
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_validators, PartialApplication, Validate},
|
||||
};
|
||||
use fancy_regex::Regex;
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub(crate) struct PatternPropertiesValidator {
|
||||
patterns: Vec<(Regex, Validators)>,
|
||||
patterns: Vec<(Regex, SchemaNode)>,
|
||||
}
|
||||
|
||||
impl PatternPropertiesValidator {
|
||||
|
@ -37,20 +39,17 @@ impl PatternPropertiesValidator {
|
|||
impl Validate for PatternPropertiesValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
if let Value::Object(item) = instance {
|
||||
self.patterns.iter().all(move |(re, validators)| {
|
||||
self.patterns.iter().all(move |(re, node)| {
|
||||
item.iter()
|
||||
.filter(move |(key, _)| re.is_match(key).unwrap_or(false))
|
||||
.all(move |(_key, value)| {
|
||||
validators
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, value))
|
||||
})
|
||||
.all(move |(_key, value)| node.is_valid(schema, value))
|
||||
})
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
|
@ -61,14 +60,12 @@ impl Validate for PatternPropertiesValidator {
|
|||
let errors: Vec<_> = self
|
||||
.patterns
|
||||
.iter()
|
||||
.flat_map(move |(re, validators)| {
|
||||
.flat_map(move |(re, node)| {
|
||||
item.iter()
|
||||
.filter(move |(key, _)| re.is_match(key).unwrap_or(false))
|
||||
.flat_map(move |(key, value)| {
|
||||
let instance_path = instance_path.push(key.clone());
|
||||
validators.iter().flat_map(move |validator| {
|
||||
validator.validate(schema, value, &instance_path)
|
||||
})
|
||||
node.validate(schema, value, &instance_path)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
@ -77,6 +74,32 @@ impl Validate for PatternPropertiesValidator {
|
|||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
if let Value::Object(item) = instance {
|
||||
let mut matched_propnames = Vec::with_capacity(item.len());
|
||||
let mut sub_results = BasicOutput::default();
|
||||
for (pattern, node) in &self.patterns {
|
||||
for (key, value) in item {
|
||||
if pattern.is_match(key).unwrap_or(false) {
|
||||
let path = instance_path.push(key.clone());
|
||||
matched_propnames.push(key.clone());
|
||||
sub_results += node.apply_rooted(schema, value, &path);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut result: PartialApplication = sub_results.into();
|
||||
result.annotate(serde_json::Value::from(matched_propnames).into());
|
||||
result
|
||||
} else {
|
||||
PartialApplication::valid_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for PatternPropertiesValidator {
|
||||
|
@ -86,7 +109,7 @@ impl core::fmt::Display for PatternPropertiesValidator {
|
|||
"patternProperties: {{{}}}",
|
||||
self.patterns
|
||||
.iter()
|
||||
.map(|(key, validators)| { format!("{}: {}", key, format_validators(validators)) })
|
||||
.map(|(key, node)| { format!("{}: {}", key, format_validators(node.validators())) })
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
|
@ -95,7 +118,7 @@ impl core::fmt::Display for PatternPropertiesValidator {
|
|||
|
||||
pub(crate) struct SingleValuePatternPropertiesValidator {
|
||||
pattern: Regex,
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
}
|
||||
|
||||
impl SingleValuePatternPropertiesValidator {
|
||||
|
@ -112,7 +135,7 @@ impl SingleValuePatternPropertiesValidator {
|
|||
Ok(r) => r,
|
||||
Err(_) => return Err(ValidationError::schema(schema)),
|
||||
},
|
||||
validators: compile_validators(schema, &pattern_context)?,
|
||||
node: compile_validators(schema, &pattern_context)?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -122,16 +145,13 @@ impl Validate for SingleValuePatternPropertiesValidator {
|
|||
if let Value::Object(item) = instance {
|
||||
item.iter()
|
||||
.filter(move |(key, _)| self.pattern.is_match(key).unwrap_or(false))
|
||||
.all(move |(_key, value)| {
|
||||
self.validators
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, value))
|
||||
})
|
||||
.all(move |(_key, value)| self.node.is_valid(schema, value))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
|
@ -144,9 +164,7 @@ impl Validate for SingleValuePatternPropertiesValidator {
|
|||
.filter(move |(key, _)| self.pattern.is_match(key).unwrap_or(false))
|
||||
.flat_map(move |(key, value)| {
|
||||
let instance_path = instance_path.push(key.clone());
|
||||
self.validators.iter().flat_map(move |validator| {
|
||||
validator.validate(schema, value, &instance_path)
|
||||
})
|
||||
self.node.validate(schema, value, &instance_path)
|
||||
})
|
||||
.collect();
|
||||
Box::new(errors.into_iter())
|
||||
|
@ -154,6 +172,30 @@ impl Validate for SingleValuePatternPropertiesValidator {
|
|||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
if let Value::Object(item) = instance {
|
||||
let mut matched_propnames = Vec::with_capacity(item.len());
|
||||
let mut outputs = BasicOutput::default();
|
||||
for (key, value) in item {
|
||||
if self.pattern.is_match(key).unwrap_or(false) {
|
||||
let path = instance_path.push(key.clone());
|
||||
matched_propnames.push(key.clone());
|
||||
outputs += self.node.apply_rooted(schema, value, &path);
|
||||
}
|
||||
}
|
||||
let mut result: PartialApplication = outputs.into();
|
||||
result.annotate(serde_json::Value::from(matched_propnames).into());
|
||||
result
|
||||
} else {
|
||||
PartialApplication::valid_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for SingleValuePatternPropertiesValidator {
|
||||
|
@ -162,7 +204,7 @@ impl core::fmt::Display for SingleValuePatternPropertiesValidator {
|
|||
f,
|
||||
"patternProperties: {{{}: {}}}",
|
||||
self.pattern,
|
||||
format_validators(&self.validators)
|
||||
format_validators(self.node.validators())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,15 @@ use crate::{
|
|||
compilation::{compile_validators, context::CompilationContext, JSONSchema},
|
||||
error::{no_error, ErrorIterator, ValidationError},
|
||||
keywords::CompilationResult,
|
||||
output::BasicOutput,
|
||||
paths::InstancePath,
|
||||
validator::{format_key_value_validators, Validate, Validators},
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_key_value_validators, PartialApplication, Validate},
|
||||
};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub(crate) struct PropertiesValidator {
|
||||
properties: Vec<(String, Validators)>,
|
||||
properties: Vec<(String, SchemaNode)>,
|
||||
}
|
||||
|
||||
impl PropertiesValidator {
|
||||
|
@ -38,19 +40,18 @@ impl PropertiesValidator {
|
|||
impl Validate for PropertiesValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
if let Value::Object(item) = instance {
|
||||
self.properties.iter().all(move |(name, validators)| {
|
||||
self.properties.iter().all(move |(name, node)| {
|
||||
let option = item.get(name);
|
||||
option.into_iter().all(move |item| {
|
||||
validators
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, item))
|
||||
})
|
||||
option
|
||||
.into_iter()
|
||||
.all(move |item| node.is_valid(schema, item))
|
||||
})
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
|
@ -61,13 +62,11 @@ impl Validate for PropertiesValidator {
|
|||
let errors: Vec<_> = self
|
||||
.properties
|
||||
.iter()
|
||||
.flat_map(move |(name, validators)| {
|
||||
.flat_map(move |(name, node)| {
|
||||
let option = item.get(name);
|
||||
option.into_iter().flat_map(move |item| {
|
||||
let instance_path = instance_path.push(name.clone());
|
||||
validators.iter().flat_map(move |validator| {
|
||||
validator.validate(schema, item, &instance_path)
|
||||
})
|
||||
node.validate(schema, item, &instance_path)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
@ -76,6 +75,30 @@ impl Validate for PropertiesValidator {
|
|||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
if let Value::Object(props) = instance {
|
||||
let mut result = BasicOutput::default();
|
||||
let mut matched_props = Vec::with_capacity(props.len());
|
||||
for (prop_name, node) in &self.properties {
|
||||
if let Some(prop) = props.get(prop_name) {
|
||||
let path = instance_path.push(prop_name.clone());
|
||||
matched_props.push(prop_name.clone());
|
||||
result += node.apply_rooted(schema, prop, &path);
|
||||
}
|
||||
}
|
||||
let mut application: PartialApplication = result.into();
|
||||
application.annotate(serde_json::Value::from(matched_props).into());
|
||||
application
|
||||
} else {
|
||||
PartialApplication::valid_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for PropertiesValidator {
|
||||
|
|
|
@ -3,12 +3,13 @@ use crate::{
|
|||
error::{error, no_error, ErrorIterator, ValidationError},
|
||||
keywords::CompilationResult,
|
||||
paths::{InstancePath, JSONPointer},
|
||||
validator::{format_validators, Validate, Validators},
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_validators, PartialApplication, Validate},
|
||||
};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub(crate) struct PropertyNamesObjectValidator {
|
||||
validators: Validators,
|
||||
node: SchemaNode,
|
||||
}
|
||||
|
||||
impl PropertyNamesObjectValidator {
|
||||
|
@ -19,7 +20,7 @@ impl PropertyNamesObjectValidator {
|
|||
) -> CompilationResult<'a> {
|
||||
let keyword_context = context.with_path("propertyNames");
|
||||
Ok(Box::new(PropertyNamesObjectValidator {
|
||||
validators: compile_validators(schema, &keyword_context)?,
|
||||
node: compile_validators(schema, &keyword_context)?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -27,17 +28,16 @@ impl PropertyNamesObjectValidator {
|
|||
impl Validate for PropertyNamesObjectValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
if let Value::Object(item) = &instance {
|
||||
self.validators.iter().all(|validator| {
|
||||
item.keys().all(move |key| {
|
||||
let wrapper = Value::String(key.to_string());
|
||||
validator.is_valid(schema, &wrapper)
|
||||
})
|
||||
item.keys().all(move |key| {
|
||||
let wrapper = Value::String(key.to_string());
|
||||
self.node.is_valid(schema, &wrapper)
|
||||
})
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
|
@ -45,25 +45,23 @@ impl Validate for PropertyNamesObjectValidator {
|
|||
instance_path: &InstancePath,
|
||||
) -> ErrorIterator<'b> {
|
||||
if let Value::Object(item) = &instance {
|
||||
let errors: Vec<_> = self
|
||||
.validators
|
||||
.iter()
|
||||
.flat_map(|validator| {
|
||||
item.keys().flat_map(move |key| {
|
||||
let wrapper = Value::String(key.to_string());
|
||||
let errors: Vec<_> = validator
|
||||
.validate(schema, &wrapper, instance_path)
|
||||
.map(|error| {
|
||||
ValidationError::property_names(
|
||||
error.schema_path.clone(),
|
||||
instance_path.into(),
|
||||
instance,
|
||||
error.into_owned(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
errors.into_iter()
|
||||
})
|
||||
let errors: Vec<_> = item
|
||||
.keys()
|
||||
.flat_map(move |key| {
|
||||
let wrapper = Value::String(key.to_string());
|
||||
let errors: Vec<_> = self
|
||||
.node
|
||||
.validate(schema, &wrapper, instance_path)
|
||||
.map(|error| {
|
||||
ValidationError::property_names(
|
||||
error.schema_path.clone(),
|
||||
instance_path.into(),
|
||||
instance,
|
||||
error.into_owned(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
errors.into_iter()
|
||||
})
|
||||
.collect();
|
||||
Box::new(errors.into_iter())
|
||||
|
@ -71,10 +69,32 @@ impl Validate for PropertyNamesObjectValidator {
|
|||
no_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
if let Value::Object(item) = instance {
|
||||
item.keys()
|
||||
.map(|key| {
|
||||
let wrapper = Value::String(key.to_string());
|
||||
self.node.apply_rooted(schema, &wrapper, instance_path)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
PartialApplication::valid_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl core::fmt::Display for PropertyNamesObjectValidator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "propertyNames: {}", format_validators(&self.validators))
|
||||
write!(
|
||||
f,
|
||||
"propertyNames: {}",
|
||||
format_validators(self.node.validators())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ use crate::{
|
|||
error::{error, ErrorIterator},
|
||||
keywords::CompilationResult,
|
||||
paths::{InstancePath, JSONPointer},
|
||||
validator::{Validate, Validators},
|
||||
schema_node::SchemaNode,
|
||||
validator::Validate,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use serde_json::Value;
|
||||
|
@ -16,7 +17,7 @@ pub(crate) struct RefValidator {
|
|||
/// at compile time without risking infinite loops of references
|
||||
/// and at the same time during validation we iterate over shared
|
||||
/// references (&self) and not owned references (&mut self).
|
||||
validators: RwLock<Option<Validators>>,
|
||||
sub_nodes: RwLock<Option<SchemaNode>>,
|
||||
schema_path: JSONPointer,
|
||||
}
|
||||
|
||||
|
@ -29,7 +30,7 @@ impl RefValidator {
|
|||
let reference = context.build_url(reference)?;
|
||||
Ok(Box::new(RefValidator {
|
||||
reference,
|
||||
validators: RwLock::new(None),
|
||||
sub_nodes: RwLock::new(None),
|
||||
schema_path: context.schema_path.clone().into(),
|
||||
}))
|
||||
}
|
||||
|
@ -37,21 +38,17 @@ impl RefValidator {
|
|||
|
||||
impl Validate for RefValidator {
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool {
|
||||
if let Some(validators) = self.validators.read().as_ref() {
|
||||
return validators
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, instance));
|
||||
if let Some(sub_nodes) = self.sub_nodes.read().as_ref() {
|
||||
return sub_nodes.is_valid(schema, instance);
|
||||
}
|
||||
if let Ok((scope, resolved)) = schema
|
||||
.resolver
|
||||
.resolve_fragment(schema.draft(), &self.reference)
|
||||
{
|
||||
let context = CompilationContext::new(scope, schema.config());
|
||||
if let Ok(validators) = compile_validators(&resolved, &context) {
|
||||
let result = validators
|
||||
.iter()
|
||||
.all(move |validator| validator.is_valid(schema, instance));
|
||||
*self.validators.write() = Some(validators);
|
||||
let context = CompilationContext::new(scope.into(), schema.config());
|
||||
if let Ok(node) = compile_validators(&resolved, &context) {
|
||||
let result = node.is_valid(schema, instance);
|
||||
*self.sub_nodes.write() = Some(node);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
@ -64,11 +61,9 @@ impl Validate for RefValidator {
|
|||
instance: &'b Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> ErrorIterator<'b> {
|
||||
if let Some(validators) = self.validators.read().as_ref() {
|
||||
if let Some(node) = self.sub_nodes.read().as_ref() {
|
||||
return Box::new(
|
||||
validators
|
||||
.iter()
|
||||
.flat_map(move |validator| validator.validate(schema, instance, instance_path))
|
||||
node.validate(schema, instance, instance_path)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter(),
|
||||
);
|
||||
|
@ -78,27 +73,21 @@ impl Validate for RefValidator {
|
|||
.resolve_fragment(schema.draft(), &self.reference)
|
||||
{
|
||||
Ok((scope, resolved)) => {
|
||||
let context = CompilationContext::new(scope, schema.config());
|
||||
let context = CompilationContext::new(scope.into(), schema.config());
|
||||
match compile_validators(&resolved, &context) {
|
||||
Ok(validators) => {
|
||||
Ok(node) => {
|
||||
let result = Box::new(
|
||||
validators
|
||||
.iter()
|
||||
.flat_map(move |validator| {
|
||||
node.err_iter(schema, instance, instance_path)
|
||||
.map(move |mut error| {
|
||||
let schema_path = self.schema_path.clone();
|
||||
validator.validate(schema, instance, instance_path).map(
|
||||
move |mut error| {
|
||||
// Prepend $ref path to the actual error
|
||||
error.schema_path = schema_path
|
||||
.extend_with(error.schema_path.as_slice());
|
||||
error
|
||||
},
|
||||
)
|
||||
error.schema_path =
|
||||
schema_path.extend_with(error.schema_path.as_slice());
|
||||
error
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter(),
|
||||
);
|
||||
*self.validators.write() = Some(validators);
|
||||
*self.sub_nodes.write() = Some(node);
|
||||
result
|
||||
}
|
||||
Err(err) => error(err.into_owned()),
|
||||
|
|
|
@ -88,9 +88,12 @@ mod content_encoding;
|
|||
mod content_media_type;
|
||||
pub mod error;
|
||||
mod keywords;
|
||||
mod output;
|
||||
pub use output::{BasicOutput, Output};
|
||||
pub mod paths;
|
||||
pub mod primitive_type;
|
||||
mod resolver;
|
||||
mod schema_node;
|
||||
mod schemas;
|
||||
mod validator;
|
||||
|
||||
|
|
|
@ -0,0 +1,280 @@
|
|||
use std::{
|
||||
collections::VecDeque,
|
||||
iter::{FromIterator, Sum},
|
||||
ops::AddAssign,
|
||||
};
|
||||
|
||||
use crate::{validator::PartialApplication, ValidationError};
|
||||
use ahash::AHashMap;
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
use crate::{
|
||||
paths::{AbsolutePath, InstancePath, JSONPointer},
|
||||
schema_node::SchemaNode,
|
||||
JSONSchema,
|
||||
};
|
||||
|
||||
/// The output format resulting from the application of a schema. This can be
|
||||
/// converted into various representations based on the definitions in
|
||||
/// <https://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.12.2>
|
||||
///
|
||||
/// Currently only the "flag" and "basic" output formats are supported
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Output<'a, 'b> {
|
||||
schema: &'a JSONSchema,
|
||||
root_node: &'a SchemaNode,
|
||||
instance: &'b serde_json::Value,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Output<'a, 'b> {
|
||||
pub(crate) const fn new<'c, 'd>(
|
||||
schema: &'c JSONSchema,
|
||||
root_node: &'c SchemaNode,
|
||||
instance: &'d serde_json::Value,
|
||||
) -> Output<'c, 'd> {
|
||||
Output {
|
||||
schema,
|
||||
root_node,
|
||||
instance,
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates whether the schema was valid, corresponds to the "flag" output
|
||||
/// format
|
||||
pub fn flag(&self) -> bool {
|
||||
self.schema.is_valid(self.instance)
|
||||
}
|
||||
|
||||
/// Output a list of errors and annotations for each element in the schema
|
||||
/// according to the basic output format
|
||||
pub fn basic(&self) -> BasicOutput<'a> {
|
||||
self.root_node
|
||||
.apply_rooted(self.schema, self.instance, &InstancePath::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// The "basic" output format
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BasicOutput<'a> {
|
||||
/// The schema was valid, collected annotations can be examined
|
||||
Valid(VecDeque<OutputUnit<Annotations<'a>>>),
|
||||
/// The schema was invalid
|
||||
Invalid(VecDeque<OutputUnit<ErrorDescription>>),
|
||||
}
|
||||
|
||||
impl<'a> BasicOutput<'a> {
|
||||
pub(crate) const fn is_valid(&self) -> bool {
|
||||
match self {
|
||||
BasicOutput::Valid(..) => true,
|
||||
BasicOutput::Invalid(..) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<OutputUnit<Annotations<'a>>> for BasicOutput<'a> {
|
||||
fn from(unit: OutputUnit<Annotations<'a>>) -> Self {
|
||||
let mut units = VecDeque::new();
|
||||
units.push_front(unit);
|
||||
BasicOutput::Valid(units)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AddAssign for BasicOutput<'a> {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
match (&mut *self, rhs) {
|
||||
(BasicOutput::Valid(ref mut anns), BasicOutput::Valid(anns_rhs)) => {
|
||||
anns.extend(anns_rhs);
|
||||
}
|
||||
(BasicOutput::Valid(..), BasicOutput::Invalid(errors)) => {
|
||||
*self = BasicOutput::Invalid(errors)
|
||||
}
|
||||
(BasicOutput::Invalid(..), BasicOutput::Valid(..)) => {}
|
||||
(BasicOutput::Invalid(errors), BasicOutput::Invalid(errors_rhs)) => {
|
||||
errors.extend(errors_rhs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sum for BasicOutput<'a> {
|
||||
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
||||
let result = BasicOutput::Valid(VecDeque::new());
|
||||
iter.fold(result, |mut acc, elem| {
|
||||
acc += elem;
|
||||
acc
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for BasicOutput<'a> {
|
||||
fn default() -> Self {
|
||||
BasicOutput::Valid(VecDeque::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<BasicOutput<'a>> for PartialApplication<'a> {
|
||||
fn from(output: BasicOutput<'a>) -> Self {
|
||||
match output {
|
||||
BasicOutput::Valid(anns) => PartialApplication::Valid {
|
||||
annotations: None,
|
||||
child_results: anns,
|
||||
},
|
||||
BasicOutput::Invalid(errors) => PartialApplication::Invalid {
|
||||
errors: Vec::new(),
|
||||
child_results: errors,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<BasicOutput<'a>> for PartialApplication<'a> {
|
||||
fn from_iter<T: IntoIterator<Item = BasicOutput<'a>>>(iter: T) -> Self {
|
||||
iter.into_iter().sum::<BasicOutput<'_>>().into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct OutputUnit<T> {
|
||||
keyword_location: JSONPointer,
|
||||
instance_location: JSONPointer,
|
||||
absolute_keyword_location: Option<AbsolutePath>,
|
||||
value: T,
|
||||
}
|
||||
|
||||
impl<T> OutputUnit<T> {
|
||||
pub(crate) const fn annotations(
|
||||
keyword_location: JSONPointer,
|
||||
instance_location: JSONPointer,
|
||||
absolute_keyword_location: Option<AbsolutePath>,
|
||||
annotations: Annotations<'_>,
|
||||
) -> OutputUnit<Annotations<'_>> {
|
||||
OutputUnit {
|
||||
keyword_location,
|
||||
instance_location,
|
||||
absolute_keyword_location,
|
||||
value: annotations,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn error(
|
||||
keyword_location: JSONPointer,
|
||||
instance_location: JSONPointer,
|
||||
absolute_keyword_location: Option<AbsolutePath>,
|
||||
error: ErrorDescription,
|
||||
) -> OutputUnit<ErrorDescription> {
|
||||
OutputUnit {
|
||||
keyword_location,
|
||||
instance_location,
|
||||
absolute_keyword_location,
|
||||
value: error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, Debug, Clone, PartialEq)]
|
||||
pub struct Annotations<'a>(AnnotationsInner<'a>);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum AnnotationsInner<'a> {
|
||||
UnmatchedKeywords(&'a AHashMap<String, serde_json::Value>),
|
||||
ValueRef(&'a serde_json::Value),
|
||||
Value(Box<serde_json::Value>),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a AHashMap<String, serde_json::Value>> for Annotations<'a> {
|
||||
fn from(anns: &'a AHashMap<String, serde_json::Value>) -> Self {
|
||||
Annotations(AnnotationsInner::UnmatchedKeywords(anns))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a serde_json::Value> for Annotations<'a> {
|
||||
fn from(v: &'a serde_json::Value) -> Self {
|
||||
Annotations(AnnotationsInner::ValueRef(v))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<serde_json::Value> for Annotations<'a> {
|
||||
fn from(v: serde_json::Value) -> Self {
|
||||
Annotations(AnnotationsInner::Value(Box::new(v)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, Debug, Clone, PartialEq)]
|
||||
pub struct ErrorDescription(String);
|
||||
|
||||
impl From<ValidationError<'_>> for ErrorDescription {
|
||||
fn from(e: ValidationError<'_>) -> Self {
|
||||
ErrorDescription(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for ErrorDescription {
|
||||
fn from(s: &'a str) -> Self {
|
||||
ErrorDescription(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> serde::Serialize for BasicOutput<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut map_ser = serializer.serialize_map(Some(2))?;
|
||||
match self {
|
||||
BasicOutput::Valid(outputs) => {
|
||||
map_ser.serialize_entry("valid", &true)?;
|
||||
map_ser.serialize_entry("annotations", outputs)?;
|
||||
}
|
||||
BasicOutput::Invalid(errors) => {
|
||||
map_ser.serialize_entry("valid", &false)?;
|
||||
map_ser.serialize_entry("errors", errors)?;
|
||||
}
|
||||
}
|
||||
map_ser.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> serde::Serialize for AnnotationsInner<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
Self::UnmatchedKeywords(kvs) => kvs.serialize(serializer),
|
||||
Self::Value(v) => v.serialize(serializer),
|
||||
Self::ValueRef(v) => v.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> serde::Serialize for OutputUnit<Annotations<'a>> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut map_ser = serializer.serialize_map(Some(4))?;
|
||||
map_ser.serialize_entry("keywordLocation", &self.keyword_location)?;
|
||||
map_ser.serialize_entry("instanceLocation", &self.instance_location)?;
|
||||
if let Some(absolute) = &self.absolute_keyword_location {
|
||||
map_ser.serialize_entry("absoluteKeywordLocation", &absolute)?;
|
||||
}
|
||||
map_ser.serialize_entry("annotations", &self.value)?;
|
||||
map_ser.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> serde::Serialize for OutputUnit<ErrorDescription> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut map_ser = serializer.serialize_map(Some(4))?;
|
||||
map_ser.serialize_entry("keywordLocation", &self.keyword_location)?;
|
||||
map_ser.serialize_entry("instanceLocation", &self.instance_location)?;
|
||||
if let Some(absolute) = &self.absolute_keyword_location {
|
||||
map_ser.serialize_entry("absoluteKeywordLocation", &absolute)?;
|
||||
}
|
||||
map_ser.serialize_entry("error", &self.value)?;
|
||||
map_ser.end()
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
//! Facilities for working with paths within schemas or validated instances.
|
||||
use std::{fmt, fmt::Write, slice::Iter};
|
||||
use std::{fmt, fmt::Write, slice::Iter, str::FromStr};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
/// JSON Pointer as a wrapper around individual path components.
|
||||
|
@ -41,11 +41,21 @@ impl JSONPointer {
|
|||
new.0.extend_from_slice(chunks);
|
||||
new
|
||||
}
|
||||
|
||||
pub(crate) fn as_slice(&self) -> &[PathChunk] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for JSONPointer {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for JSONPointer {
|
||||
fn default() -> Self {
|
||||
JSONPointer(Vec::new())
|
||||
|
@ -209,6 +219,53 @@ impl From<&[PathChunk]> for JSONPointer {
|
|||
}
|
||||
}
|
||||
|
||||
/// An absolute reference
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AbsolutePath(url::Url);
|
||||
|
||||
impl AbsolutePath {
|
||||
pub(crate) fn with_path(&self, path: &str) -> Self {
|
||||
let mut result = self.0.clone();
|
||||
result.set_path(path);
|
||||
AbsolutePath(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for AbsolutePath {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.0.to_string().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AbsolutePath {
|
||||
type Err = url::ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
url::Url::parse(s).map(AbsolutePath::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AbsolutePath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AbsolutePath> for url::Url {
|
||||
fn from(p: AbsolutePath) -> Self {
|
||||
p.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<url::Url> for AbsolutePath {
|
||||
fn from(u: url::Url) -> Self {
|
||||
AbsolutePath(u)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::JSONPointer;
|
||||
|
|
|
@ -0,0 +1,427 @@
|
|||
use crate::{
|
||||
compilation::context::CompilationContext,
|
||||
error::ErrorIterator,
|
||||
keywords::BoxedValidator,
|
||||
output::{Annotations, BasicOutput, ErrorDescription, OutputUnit},
|
||||
paths::{AbsolutePath, InstancePath, JSONPointer},
|
||||
validator::{format_validators, PartialApplication, Validate},
|
||||
JSONSchema,
|
||||
};
|
||||
use ahash::AHashMap;
|
||||
use std::{collections::VecDeque, fmt};
|
||||
|
||||
/// A node in the schema tree, returned by [`compile_validators`]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SchemaNode {
|
||||
validators: NodeValidators,
|
||||
relative_path: JSONPointer,
|
||||
absolute_path: Option<AbsolutePath>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NodeValidators {
|
||||
/// The result of compiling a boolean valued schema, e.g
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "additionalProperties": false
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Here the result of `compile_validators` called with the `false` value will return a
|
||||
/// `SchemaNode` with a single `BooleanValidator` as it's `validators`.
|
||||
Boolean { validator: Option<BoxedValidator> },
|
||||
/// The result of compiling a schema which is composed of keywords (almost all schemas)
|
||||
Keyword(Box<KeywordValidators>),
|
||||
/// The result of compiling a schema which is "array valued", e.g the "dependencies" keyword of
|
||||
/// draft 7 which can take values which are an array of other property names
|
||||
Array { validators: Vec<BoxedValidator> },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct KeywordValidators {
|
||||
/// The keywords on this node which were not recognized by any vocabularies. These are
|
||||
/// stored so we can later produce them as annotations
|
||||
unmatched_keywords: Option<AHashMap<String, serde_json::Value>>,
|
||||
// We should probably use AHashMap here but it breaks a bunch of test which assume
|
||||
// validators are in a particular order
|
||||
validators: Vec<(String, BoxedValidator)>,
|
||||
}
|
||||
|
||||
impl SchemaNode {
|
||||
pub(crate) fn new_from_boolean(
|
||||
context: &CompilationContext<'_>,
|
||||
validator: Option<BoxedValidator>,
|
||||
) -> SchemaNode {
|
||||
SchemaNode {
|
||||
relative_path: context.clone().into_pointer(),
|
||||
absolute_path: context.base_uri().map(AbsolutePath::from),
|
||||
validators: NodeValidators::Boolean { validator },
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_from_keywords(
|
||||
context: &CompilationContext<'_>,
|
||||
mut validators: Vec<(String, BoxedValidator)>,
|
||||
unmatched_keywords: Option<AHashMap<String, serde_json::Value>>,
|
||||
) -> SchemaNode {
|
||||
validators.shrink_to_fit();
|
||||
SchemaNode {
|
||||
relative_path: context.clone().into_pointer(),
|
||||
absolute_path: context.base_uri().map(AbsolutePath::from),
|
||||
validators: NodeValidators::Keyword(Box::new(KeywordValidators {
|
||||
unmatched_keywords,
|
||||
validators,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_from_array(
|
||||
context: &CompilationContext<'_>,
|
||||
mut validators: Vec<BoxedValidator>,
|
||||
) -> SchemaNode {
|
||||
validators.shrink_to_fit();
|
||||
SchemaNode {
|
||||
relative_path: context.clone().into_pointer(),
|
||||
absolute_path: context.base_uri().map(AbsolutePath::from),
|
||||
validators: NodeValidators::Array { validators },
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn validators(&self) -> impl Iterator<Item = &BoxedValidator> + ExactSizeIterator {
|
||||
match &self.validators {
|
||||
NodeValidators::Boolean { validator } => {
|
||||
if let Some(v) = validator {
|
||||
NodeValidatorsIter::BooleanValidators(std::iter::once(v))
|
||||
} else {
|
||||
NodeValidatorsIter::NoValidator
|
||||
}
|
||||
}
|
||||
NodeValidators::Keyword(kvals) => {
|
||||
NodeValidatorsIter::KeywordValidators(kvals.validators.iter())
|
||||
}
|
||||
NodeValidators::Array { validators } => {
|
||||
NodeValidatorsIter::ArrayValidators(validators.iter())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_validators(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", format_validators(self.validators()))
|
||||
}
|
||||
|
||||
/// This is similar to `Validate::apply` except that `SchemaNode` knows where it is in the
|
||||
/// validator tree and so rather than returning a `PartialApplication` it is able to return a
|
||||
/// complete `BasicOutput`. This is the mechanism which compositional validators use to combine
|
||||
/// results from sub-schemas
|
||||
pub(crate) fn apply_rooted(
|
||||
&self,
|
||||
schema: &JSONSchema,
|
||||
instance: &serde_json::Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> BasicOutput {
|
||||
match self.apply(schema, instance, instance_path) {
|
||||
PartialApplication::Valid {
|
||||
annotations,
|
||||
mut child_results,
|
||||
} => {
|
||||
if let Some(annotations) = annotations {
|
||||
child_results.insert(0, self.annotation_at(instance_path, annotations));
|
||||
};
|
||||
BasicOutput::Valid(child_results)
|
||||
}
|
||||
PartialApplication::Invalid {
|
||||
errors,
|
||||
mut child_results,
|
||||
} => {
|
||||
for error in errors {
|
||||
child_results.insert(0, self.error_at(instance_path, error));
|
||||
}
|
||||
BasicOutput::Invalid(child_results)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an error output which is marked as occurring at this schema node
|
||||
pub(crate) fn error_at(
|
||||
&self,
|
||||
instance_path: &InstancePath,
|
||||
error: ErrorDescription,
|
||||
) -> OutputUnit<ErrorDescription> {
|
||||
OutputUnit::<ErrorDescription>::error(
|
||||
self.relative_path.clone(),
|
||||
instance_path.clone().into(),
|
||||
self.absolute_path.clone(),
|
||||
error,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create an anontation output which is marked as occurring at this schema node
|
||||
pub(crate) fn annotation_at<'a>(
|
||||
&self,
|
||||
instance_path: &InstancePath,
|
||||
annotations: Annotations<'a>,
|
||||
) -> OutputUnit<Annotations<'a>> {
|
||||
OutputUnit::<Annotations<'_>>::annotations(
|
||||
self.relative_path.clone(),
|
||||
instance_path.clone().into(),
|
||||
self.absolute_path.clone(),
|
||||
annotations,
|
||||
)
|
||||
}
|
||||
|
||||
/// Here we return a `NodeValidatorsErrIter` to avoid allocating in some situations. This isn't
|
||||
/// always possible but for a lot of common cases (e.g nodes with a single child) we can do it.
|
||||
/// This is wrapped in a `Box` by `SchemaNode::validate`
|
||||
pub(crate) fn err_iter<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
instance: &'b serde_json::Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> NodeValidatorsErrIter<'b> {
|
||||
match &self.validators {
|
||||
NodeValidators::Keyword(kvs) if kvs.validators.len() == 1 => {
|
||||
NodeValidatorsErrIter::Single(kvs.validators[0].1.validate(
|
||||
schema,
|
||||
instance,
|
||||
instance_path,
|
||||
))
|
||||
}
|
||||
NodeValidators::Keyword(kvs) => NodeValidatorsErrIter::Multiple(
|
||||
kvs.validators
|
||||
.iter()
|
||||
.flat_map(|(_, v)| v.validate(schema, instance, instance_path))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter(),
|
||||
),
|
||||
NodeValidators::Boolean {
|
||||
validator: Some(v), ..
|
||||
} => NodeValidatorsErrIter::Single(v.validate(schema, instance, instance_path)),
|
||||
NodeValidators::Boolean {
|
||||
validator: None, ..
|
||||
} => NodeValidatorsErrIter::NoErrs,
|
||||
NodeValidators::Array { validators } => NodeValidatorsErrIter::Multiple(
|
||||
validators
|
||||
.iter()
|
||||
.flat_map(move |v| v.validate(schema, instance, instance_path))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to apply an iterator of `(Into<PathChunk>, Validate)` to a value. This is
|
||||
/// useful as a keyword schemanode has a set of validators keyed by their keywords, so the
|
||||
/// `Into<Pathchunk>` is a `String` whereas an array schemanode has an array of validators so
|
||||
/// the `Into<PathChunk>` is a `usize`
|
||||
fn apply_subschemas<'a, I, P>(
|
||||
&self,
|
||||
schema: &JSONSchema,
|
||||
instance: &serde_json::Value,
|
||||
instance_path: &InstancePath,
|
||||
path_and_validators: I,
|
||||
annotations: Option<Annotations<'a>>,
|
||||
) -> PartialApplication<'a>
|
||||
where
|
||||
I: Iterator<Item = (P, &'a Box<dyn Validate + Send + Sync + 'a>)> + 'a,
|
||||
P: Into<crate::paths::PathChunk>,
|
||||
P: std::fmt::Display,
|
||||
{
|
||||
let mut success_results: VecDeque<OutputUnit<Annotations>> = VecDeque::new();
|
||||
let mut error_results = VecDeque::new();
|
||||
for (path, validator) in path_and_validators {
|
||||
let path = self.relative_path.extend_with(&[path.into()]);
|
||||
let absolute_path = self
|
||||
.absolute_path
|
||||
.clone()
|
||||
.map(|p| p.with_path(path.to_string().as_str()));
|
||||
match validator.apply(schema, instance, instance_path) {
|
||||
PartialApplication::Valid {
|
||||
annotations,
|
||||
child_results,
|
||||
} => {
|
||||
if let Some(annotations) = annotations {
|
||||
success_results.push_front(OutputUnit::<Annotations<'a>>::annotations(
|
||||
path,
|
||||
instance_path.into(),
|
||||
absolute_path,
|
||||
annotations,
|
||||
));
|
||||
}
|
||||
success_results.extend(child_results);
|
||||
}
|
||||
PartialApplication::Invalid {
|
||||
errors: these_errors,
|
||||
child_results,
|
||||
} => {
|
||||
error_results.extend(child_results);
|
||||
error_results.extend(these_errors.into_iter().map(|error| {
|
||||
OutputUnit::<ErrorDescription>::error(
|
||||
path.clone(),
|
||||
instance_path.into(),
|
||||
absolute_path.clone(),
|
||||
error,
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
if !error_results.is_empty() {
|
||||
PartialApplication::Invalid {
|
||||
errors: Vec::new(),
|
||||
child_results: error_results,
|
||||
}
|
||||
} else {
|
||||
PartialApplication::Valid {
|
||||
annotations,
|
||||
child_results: success_results,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SchemaNode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.format_validators(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Validate for SchemaNode {
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
schema: &'a JSONSchema,
|
||||
instance: &'b serde_json::Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> ErrorIterator<'b> {
|
||||
return Box::new(self.err_iter(schema, instance, instance_path));
|
||||
}
|
||||
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &serde_json::Value) -> bool {
|
||||
match &self.validators {
|
||||
// If we only have one validator then calling it's `is_valid` directly does
|
||||
// actually save the 20 or so instructions required to call the `slice::Iter::all`
|
||||
// implementation. Validators at the leaf of a tree are all single node validators so
|
||||
// this optimization can have significant cumulative benefits
|
||||
NodeValidators::Keyword(kvs) if kvs.validators.len() == 1 => {
|
||||
kvs.validators[0].1.is_valid(schema, instance)
|
||||
}
|
||||
NodeValidators::Keyword(kvs) => kvs
|
||||
.validators
|
||||
.iter()
|
||||
.all(|(_, v)| v.is_valid(schema, instance)),
|
||||
NodeValidators::Array { validators } => {
|
||||
validators.iter().all(|v| v.is_valid(schema, instance))
|
||||
}
|
||||
NodeValidators::Boolean {
|
||||
validator: Some(v), ..
|
||||
} => v.is_valid(schema, instance),
|
||||
NodeValidators::Boolean {
|
||||
validator: None, ..
|
||||
} => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &serde_json::Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
match self.validators {
|
||||
NodeValidators::Array { ref validators } => self.apply_subschemas(
|
||||
schema,
|
||||
instance,
|
||||
instance_path,
|
||||
validators.iter().enumerate(),
|
||||
None,
|
||||
),
|
||||
NodeValidators::Boolean { ref validator } => {
|
||||
if let Some(validator) = validator {
|
||||
validator.apply(schema, instance, instance_path)
|
||||
} else {
|
||||
PartialApplication::Valid {
|
||||
annotations: None,
|
||||
child_results: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeValidators::Keyword(ref kvals) => {
|
||||
let KeywordValidators {
|
||||
ref unmatched_keywords,
|
||||
ref validators,
|
||||
} = **kvals;
|
||||
let annotations: Option<Annotations<'a>> =
|
||||
unmatched_keywords.as_ref().map(Annotations::from);
|
||||
self.apply_subschemas(
|
||||
schema,
|
||||
instance,
|
||||
instance_path,
|
||||
validators.iter().map(|(p, v)| (p.clone(), v)),
|
||||
annotations,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum NodeValidatorsIter<'a> {
|
||||
NoValidator,
|
||||
BooleanValidators(std::iter::Once<&'a BoxedValidator>),
|
||||
KeywordValidators(std::slice::Iter<'a, (String, BoxedValidator)>),
|
||||
ArrayValidators(std::slice::Iter<'a, BoxedValidator>),
|
||||
}
|
||||
|
||||
impl<'a> Iterator for NodeValidatorsIter<'a> {
|
||||
type Item = &'a BoxedValidator;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::NoValidator => None,
|
||||
Self::BooleanValidators(i) => i.next(),
|
||||
Self::KeywordValidators(v) => v.next().map(|(_, v)| v),
|
||||
Self::ArrayValidators(v) => v.next(),
|
||||
}
|
||||
}
|
||||
|
||||
fn all<F>(&mut self, mut f: F) -> bool
|
||||
where
|
||||
Self: Sized,
|
||||
F: FnMut(Self::Item) -> bool,
|
||||
{
|
||||
match self {
|
||||
Self::NoValidator => true,
|
||||
Self::BooleanValidators(i) => i.all(f),
|
||||
Self::KeywordValidators(v) => v.all(|(_, v)| f(v)),
|
||||
Self::ArrayValidators(v) => v.all(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ExactSizeIterator for NodeValidatorsIter<'a> {
|
||||
fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::NoValidator => 0,
|
||||
Self::BooleanValidators(..) => 1,
|
||||
Self::KeywordValidators(v) => v.len(),
|
||||
Self::ArrayValidators(v) => v.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum NodeValidatorsErrIter<'a> {
|
||||
NoErrs,
|
||||
Single(ErrorIterator<'a>),
|
||||
Multiple(std::vec::IntoIter<crate::error::ValidationError<'a>>),
|
||||
}
|
||||
|
||||
impl<'a> Iterator for NodeValidatorsErrIter<'a> {
|
||||
type Item = crate::error::ValidationError<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::NoErrs => None,
|
||||
Self::Single(i) => i.next(),
|
||||
Self::Multiple(ms) => ms.next(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,28 @@
|
|||
use crate::{
|
||||
compilation::JSONSchema, error::ErrorIterator, keywords::BoxedValidator, paths::InstancePath,
|
||||
compilation::JSONSchema,
|
||||
error::ErrorIterator,
|
||||
keywords::BoxedValidator,
|
||||
output::{Annotations, ErrorDescription, OutputUnit},
|
||||
paths::InstancePath,
|
||||
schema_node::SchemaNode,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use std::fmt;
|
||||
use std::{collections::VecDeque, fmt};
|
||||
|
||||
/// The Validate trait represents a predicate over some JSON value. Some validators are very simple
|
||||
/// predicates such as "a value which is a string", whereas others may be much more complex,
|
||||
/// consisting of several other validators composed together in various ways.
|
||||
///
|
||||
/// Much of the time all an application cares about is whether the predicate returns true or false,
|
||||
/// in that case the `is_valid` function is sufficient. Sometimes applications will want more
|
||||
/// detail about why a schema has failed, in which case the `validate` method can be used to
|
||||
/// iterate over the errors produced by this validator. Finally, applications may be interested in
|
||||
/// annotations produced by schemas over valid results, in this case the `apply` method can be used
|
||||
/// to obtain this information.
|
||||
///
|
||||
/// If you are implementing `Validate` it is often sufficient to implement `validate` and
|
||||
/// `is_valid`. `apply` is only necessary for validators which compose other validators. See the
|
||||
/// documentation for `apply` for more information.
|
||||
pub(crate) trait Validate: Send + Sync + core::fmt::Display {
|
||||
fn validate<'a, 'b>(
|
||||
&self,
|
||||
|
@ -15,6 +34,127 @@ pub(crate) trait Validate: Send + Sync + core::fmt::Display {
|
|||
// It is faster for cases when the result is not needed (like anyOf), since errors are
|
||||
// not constructed
|
||||
fn is_valid(&self, schema: &JSONSchema, instance: &Value) -> bool;
|
||||
|
||||
/// `apply` applies this validator and any sub-validators it is composed of to the value in
|
||||
/// question and collects the resulting annotations or errors. Note that the result of `apply`
|
||||
/// is a `PartialApplication`.
|
||||
///
|
||||
/// What does "partial" mean in this context? Each valdator can produce annotations or errors
|
||||
/// in the case of successful or unsuccessful validation respectively. We're ultimately
|
||||
/// producing these errors and annotations to produce the "basic" output format as specified in
|
||||
/// the 2020-12 draft specification. In this format each annotation or error must include a
|
||||
/// json pointer to the keyword in the schema and to the property in the instance. However,
|
||||
/// most validators don't know where they are in the schema tree so we allow them to return the
|
||||
/// errors or annotations they produce directly and leave it up to the parent validator to fill
|
||||
/// in the path information. This means that only validators which are composed of other
|
||||
/// validators must implement `apply`, for validators on the leaves of the validator tree the
|
||||
/// default implementation which is defined in terms of `validate` will suffice.
|
||||
///
|
||||
/// If you are writing a validator which is composed of other validators then your validator will
|
||||
/// need to store references to the `SchemaNode`s which contain those other validators.
|
||||
/// `SchemaNode` stores information about where it is in the schema tree and therefore provides an
|
||||
/// `apply_rooted` method which returns a full `BasicOutput`. `BasicOutput` implements `AddAssign`
|
||||
/// so a typical pattern is to compose results from sub validators using `+=` and then use the
|
||||
/// `From<BasicOutput> for PartialApplication` impl to convert the composed outputs into a
|
||||
/// `PartialApplication` to return. For example, here is the implementation of
|
||||
/// `IfThenValidator`
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // Note that self.schema is a `SchemaNode` and we use `apply_rooted` to return a `BasicOutput`
|
||||
/// let mut if_result = self.schema.apply_rooted(schema, instance, instance_path);
|
||||
/// if if_result.is_valid() {
|
||||
/// // here we use the `AddAssign` implementation to combine the results of subschemas
|
||||
/// if_result += self
|
||||
/// .then_schema
|
||||
/// .apply_rooted(schema, instance, instance_path);
|
||||
/// // Here we use the `From<BasicOutput> for PartialApplication impl
|
||||
/// if_result.into()
|
||||
/// } else {
|
||||
/// self.else_schema
|
||||
/// .apply_rooted(schema, instance, instance_path)
|
||||
/// .into()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// `BasicOutput` also implements `Sum<BasicOutput>` and `FromIterator<BasicOutput<'a>> for PartialApplication<'a>`
|
||||
/// so you can use `sum()` and `collect()` in simple cases.
|
||||
fn apply<'a>(
|
||||
&'a self,
|
||||
schema: &JSONSchema,
|
||||
instance: &Value,
|
||||
instance_path: &InstancePath,
|
||||
) -> PartialApplication<'a> {
|
||||
let errors: Vec<ErrorDescription> = self
|
||||
.validate(schema, instance, instance_path)
|
||||
.map(ErrorDescription::from)
|
||||
.collect();
|
||||
if errors.is_empty() {
|
||||
PartialApplication::valid_empty()
|
||||
} else {
|
||||
PartialApplication::invalid_empty(errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of applying a validator to an instance. As explained in the documentation for
|
||||
/// `Validate::apply` this is a "partial" result because it does not include information about
|
||||
/// where the error or annotation occurred.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub(crate) enum PartialApplication<'a> {
|
||||
Valid {
|
||||
/// Annotations produced by this validator
|
||||
annotations: Option<Annotations<'a>>,
|
||||
/// Any outputs produced by validators which are children of this validator
|
||||
child_results: VecDeque<OutputUnit<Annotations<'a>>>,
|
||||
},
|
||||
Invalid {
|
||||
/// Errors which caused this schema to be invalid
|
||||
errors: Vec<ErrorDescription>,
|
||||
/// Any error outputs produced by child validators of this validator
|
||||
child_results: VecDeque<OutputUnit<ErrorDescription>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> PartialApplication<'a> {
|
||||
/// Create an empty `PartialApplication` which is valid
|
||||
pub(crate) fn valid_empty() -> PartialApplication<'static> {
|
||||
PartialApplication::Valid {
|
||||
annotations: None,
|
||||
child_results: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an empty `PartialApplication` which is invalid
|
||||
pub(crate) fn invalid_empty(errors: Vec<ErrorDescription>) -> PartialApplication<'static> {
|
||||
PartialApplication::Invalid {
|
||||
errors,
|
||||
child_results: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the annotation that will be returned for the current validator. If this
|
||||
/// `PartialApplication` is invalid then this method does nothing
|
||||
pub(crate) fn annotate(&mut self, new_annotations: Annotations<'a>) {
|
||||
match self {
|
||||
Self::Valid { annotations, .. } => *annotations = Some(new_annotations),
|
||||
Self::Invalid { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the error that will be returned for the current validator. If this
|
||||
/// `PartialApplication` is valid then this method converts this application into
|
||||
/// `PartialApplication::Invalid`
|
||||
pub(crate) fn mark_errored(&mut self, error: ErrorDescription) {
|
||||
match self {
|
||||
Self::Invalid { errors, .. } => errors.push(error),
|
||||
Self::Valid { .. } => {
|
||||
*self = Self::Invalid {
|
||||
errors: vec![error],
|
||||
child_results: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for dyn Validate + Send + Sync {
|
||||
|
@ -23,13 +163,15 @@ impl fmt::Debug for dyn Validate + Send + Sync {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) type Validators = Vec<BoxedValidator>;
|
||||
|
||||
pub(crate) fn format_validators(validators: &[BoxedValidator]) -> String {
|
||||
pub(crate) fn format_validators<'a, I: ExactSizeIterator + Iterator<Item = &'a BoxedValidator>>(
|
||||
mut validators: I,
|
||||
) -> String {
|
||||
match validators.len() {
|
||||
0 => "{}".to_string(),
|
||||
1 => {
|
||||
let name = validators[0].to_string();
|
||||
// Unwrap is okay due to the check on len
|
||||
let validator = validators.next().unwrap();
|
||||
let name = validator.to_string();
|
||||
match name.as_str() {
|
||||
// boolean validators are represented as is, without brackets because if they
|
||||
// occur in a vector, then the schema is not a key/value mapping
|
||||
|
@ -40,7 +182,6 @@ pub(crate) fn format_validators(validators: &[BoxedValidator]) -> String {
|
|||
_ => format!(
|
||||
"{{{}}}",
|
||||
validators
|
||||
.iter()
|
||||
.map(|validator| format!("{:?}", validator))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
|
@ -48,18 +189,21 @@ pub(crate) fn format_validators(validators: &[BoxedValidator]) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn format_vec_of_validators(validators: &[Validators]) -> String {
|
||||
pub(crate) fn format_iter_of_validators<'a, G, I>(validators: I) -> String
|
||||
where
|
||||
I: Iterator<Item = G>,
|
||||
G: ExactSizeIterator + Iterator<Item = &'a BoxedValidator>,
|
||||
{
|
||||
validators
|
||||
.iter()
|
||||
.map(|v| format_validators(v))
|
||||
.map(format_validators)
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
pub(crate) fn format_key_value_validators(validators: &[(String, Validators)]) -> String {
|
||||
pub(crate) fn format_key_value_validators(validators: &[(String, SchemaNode)]) -> String {
|
||||
validators
|
||||
.iter()
|
||||
.map(|(name, validators)| format!("{}: {}", name, format_validators(validators)))
|
||||
.map(|(name, node)| format!("{}: {}", name, format_validators(node.validators())))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue