Files
jspg/validator/src/validator.rs

1245 lines
32 KiB
Rust

use std::{borrow::Cow, cmp::min, collections::HashSet, fmt::Write};
use ahash::AHashSet;
use serde_json::{Map, Value};
use crate::{util::*, *};
#[derive(Default, Clone)]
struct Override<'s>(AHashSet<&'s str>);
macro_rules! prop {
($prop:expr) => {
InstanceToken::Prop(Cow::Borrowed($prop))
};
}
macro_rules! item {
($item:expr) => {
InstanceToken::Item($item)
};
}
pub(crate) fn validate<'s, 'v>(
v: &'v Value,
schema: &'s Schema,
schemas: &'s Schemas,
options: Option<ValidationOptions>,
) -> Result<(), ValidationError<'s, 'v>> {
let scope = Scope {
sch: schema.idx,
ref_kw: None,
vid: 0,
parent: None,
};
let mut vloc = Vec::with_capacity(8);
let options = options.unwrap_or_default();
let (result, _) = Validator {
v,
vloc: &mut vloc,
schema,
schemas,
scope,
options,
overrides: Override::default(), // Start with an empty override context
uneval: Uneval::from(v, schema, options.be_strict),
errors: vec![],
bool_result: false,
}
.validate();
match result {
Err(err) => {
let mut e = ValidationError {
schema_url: &schema.loc,
instance_location: InstanceLocation::new(),
kind: ErrorKind::Schema { url: &schema.loc },
causes: vec![],
};
if let ErrorKind::Group = err.kind {
e.causes = err.causes;
} else {
e.causes.push(err);
}
Err(e)
}
Ok(_) => Ok(()),
}
}
macro_rules! kind {
($kind:ident, $name:ident: $value:expr) => {
ErrorKind::$kind { $name: $value }
};
($kind:ident, $got:expr, $want:expr) => {
ErrorKind::$kind {
got: $got,
want: $want,
}
};
($kind:ident, $got:expr, $want:expr, $err:expr) => {
ErrorKind::$kind {
got: $got,
want: $want,
err: $err,
}
};
($kind: ident) => {
ErrorKind::$kind
};
}
struct Validator<'v, 's, 'd, 'e> {
v: &'v Value,
vloc: &'e mut Vec<InstanceToken<'v>>,
schema: &'s Schema,
schemas: &'s Schemas,
scope: Scope<'d>,
options: ValidationOptions,
overrides: Override<'s>,
uneval: Uneval<'v>,
errors: Vec<ValidationError<'s, 'v>>,
bool_result: bool, // is interested to know valid or not (but not actuall error)
}
impl<'v, 's> Validator<'v, 's, '_, '_> {
fn validate(mut self) -> (Result<(), ValidationError<'s, 'v>>, Uneval<'v>) {
let s = self.schema;
let v = self.v;
// boolean --
if let Some(b) = s.boolean {
return match b {
false => (Err(self.error(kind!(FalseSchema))), self.uneval),
true => (Ok(()), self.uneval),
};
}
// check cycle --
if let Some(scp) = self.scope.check_cycle() {
let kind = ErrorKind::RefCycle {
url: &self.schema.loc,
kw_loc1: self.kw_loc(&self.scope),
kw_loc2: self.kw_loc(scp),
};
return (Err(self.error(kind)), self.uneval);
}
// type --
if !s.types.is_empty() {
let v_type = Type::of(v);
let matched =
s.types.contains(v_type) || (s.types.contains(Type::Integer) && is_integer(v));
if !matched {
return (Err(self.error(kind!(Type, v_type, s.types))), self.uneval);
}
}
// constant --
if let Some(c) = &s.constant {
if !equals(v, c) {
return (Err(self.error(kind!(Const, want: c))), self.uneval);
}
}
// enum --
if let Some(Enum { types, values }) = &s.enum_ {
if !types.contains(Type::of(v)) || !values.iter().any(|e| equals(e, v)) {
return (Err(self.error(kind!(Enum, want: values))), self.uneval);
}
}
// format --
if let Some(format) = &s.format {
if let Err(e) = (format.func)(v) {
self.add_error(kind!(Format, Cow::Borrowed(v), format.name, e));
}
}
// type specific validations --
match v {
Value::Object(obj) => self.obj_validate(obj),
Value::Array(arr) => self.arr_validate(arr),
Value::String(str) => self.str_validate(str),
Value::Number(num) => self.num_validate(num),
_ => {}
}
// $ref --
if let Some(ref_) = s.ref_ {
let result = self.validate_ref(ref_, "$ref");
if s.draft_version < 2019 {
return (result, self.uneval);
}
self.errors.extend(result.err());
}
if self.errors.is_empty() || !self.bool_result {
if s.draft_version >= 2019 {
self.refs_validate();
}
self.cond_validate();
if s.draft_version >= 2019 {
self.uneval_validate();
}
}
match self.errors.len() {
0 => (Ok(()), self.uneval),
1 => (Err(self.errors.remove(0)), self.uneval),
_ => {
let mut e = self.error(kind!(Group));
e.causes = self.errors;
(Err(e), self.uneval)
}
}
}
}
// type specific validations
impl<'v> Validator<'v, '_, '_,'_> {
fn obj_validate(&mut self, obj: &'v Map<String, Value>) {
let s = self.schema;
macro_rules! add_err {
($result:expr) => {
if let Err(e) = $result {
self.errors.push(e);
}
};
}
// minProperties --
if let Some(min) = s.min_properties {
if obj.len() < min {
self.add_error(kind!(MinProperties, obj.len(), min));
}
}
// maxProperties --
if let Some(max) = s.max_properties {
if obj.len() > max {
self.add_error(kind!(MaxProperties, obj.len(), max));
}
}
// required --
if !s.required.is_empty() {
if let Some(missing) = self.find_missing(obj, &s.required) {
self.add_error(kind!(Required, want: missing));
}
}
if self.bool_result && !self.errors.is_empty() {
return;
}
// dependencies --
for (prop, dep) in &s.dependencies {
if obj.contains_key(prop) {
match dep {
Dependency::Props(required) => {
if let Some(missing) = self.find_missing(obj, required) {
self.add_error(ErrorKind::Dependency { prop, missing });
}
}
Dependency::SchemaRef(sch) => {
add_err!(self.validate_self(*sch));
}
}
}
}
let mut additional_props = vec![];
for (pname, pvalue) in obj {
if self.overrides.0.contains(pname.as_str()) {
self.uneval.props.remove(pname);
continue;
}
if self.bool_result && !self.errors.is_empty() {
return;
}
let mut evaluated = false;
// properties --
if let Some(sch) = s.properties.get(pname) {
evaluated = true;
add_err!(self.validate_val(*sch, pvalue, prop!(pname)));
}
// patternProperties --
for (regex, sch) in &s.pattern_properties {
if regex.is_match(pname) {
evaluated = true;
add_err!(self.validate_val(*sch, pvalue, prop!(pname)));
}
}
if !evaluated {
// additionalProperties --
if let Some(additional) = &s.additional_properties {
evaluated = true;
match additional {
Additional::Bool(allowed) => {
if !allowed {
additional_props.push(pname.into());
}
}
Additional::SchemaRef(sch) => {
add_err!(self.validate_val(*sch, pvalue, prop!(pname)));
}
}
}
}
if evaluated {
self.uneval.props.remove(pname);
}
}
if !additional_props.is_empty() {
self.add_error(kind!(AdditionalProperties, got: additional_props));
}
if s.draft_version == 4 {
return;
}
// propertyNames --
if let Some(sch) = &s.property_names {
for pname in obj.keys() {
let v = Value::String(pname.to_owned());
if let Err(mut e) = self.schemas.validate(&v, *sch, Some(self.options)) {
e.schema_url = &s.loc;
e.kind = ErrorKind::PropertyName {
prop: pname.to_owned(),
};
self.errors.push(e.clone_static());
}
}
}
if s.draft_version == 6 {
return;
}
// dependentSchemas --
for (pname, sch) in &s.dependent_schemas {
if obj.contains_key(pname) {
add_err!(self.validate_self(*sch));
}
}
// dependentRequired --
for (prop, required) in &s.dependent_required {
if obj.contains_key(prop) {
if let Some(missing) = self.find_missing(obj, required) {
self.add_error(ErrorKind::DependentRequired { prop, missing });
}
}
}
}
fn arr_validate(&mut self, arr: &'v Vec<Value>) {
let s = self.schema;
let len = arr.len();
macro_rules! add_err {
($result:expr) => {
if let Err(e) = $result {
self.errors.push(e);
}
};
}
// minItems --
if let Some(min) = s.min_items {
if len < min {
self.add_error(kind!(MinItems, len, min));
}
}
// maxItems --
if let Some(max) = s.max_items {
if len > max {
self.add_error(kind!(MaxItems, len, max));
}
}
// uniqueItems --
if len > 1 && s.unique_items {
if let Some((i, j)) = duplicates(arr) {
self.add_error(kind!(UniqueItems, got: [i, j]));
}
}
if s.draft_version < 2020 {
let mut evaluated = 0;
// items --
if let Some(items) = &s.items {
match items {
Items::SchemaRef(sch) => {
for (i, item) in arr.iter().enumerate() {
add_err!(self.validate_val(*sch, item, item!(i)));
}
evaluated = len;
// debug_assert!(self.uneval.items.is_empty());
}
Items::SchemaRefs(list) => {
for (i, (item, sch)) in arr.iter().zip(list).enumerate() {
add_err!(self.validate_val(*sch, item, item!(i)));
}
evaluated = min(list.len(), len);
}
}
}
// additionalItems --
if let Some(additional) = &s.additional_items {
match additional {
Additional::Bool(allowed) => {
if !allowed && evaluated != len {
self.add_error(kind!(AdditionalItems, got: len - evaluated));
}
}
Additional::SchemaRef(sch) => {
for (i, item) in arr[evaluated..].iter().enumerate() {
add_err!(self.validate_val(*sch, item, item!(i)));
}
}
}
// debug_assert!(self.uneval.items.is_empty());
}
} else {
// prefixItems --
for (i, (sch, item)) in s.prefix_items.iter().zip(arr).enumerate() {
add_err!(self.validate_val(*sch, item, item!(i)));
}
// items2020 --
if let Some(sch) = &s.items2020 {
let evaluated = min(s.prefix_items.len(), len);
for (i, item) in arr[evaluated..].iter().enumerate() {
add_err!(self.validate_val(*sch, item, item!(i)));
}
// debug_assert!(self.uneval.items.is_empty());
}
}
// contains --
if let Some(sch) = &s.contains {
let mut matched = vec![];
let mut errors = vec![];
for (i, item) in arr.iter().enumerate() {
if let Err(e) = self.validate_val(*sch, item, item!(i)) {
errors.push(e);
} else {
matched.push(i);
if s.draft_version >= 2020 {
self.uneval.items.remove(&i);
}
}
}
// minContains --
if let Some(min) = s.min_contains {
if matched.len() < min {
let mut e = self.error(kind!(MinContains, matched.clone(), min));
e.causes = errors;
self.errors.push(e);
}
} else if matched.is_empty() {
let mut e = self.error(kind!(Contains));
e.causes = errors;
self.errors.push(e);
}
// maxContains --
if let Some(max) = s.max_contains {
if matched.len() > max {
self.add_error(kind!(MaxContains, matched, max));
}
}
}
}
fn str_validate(&mut self, str: &'v String) {
let s = self.schema;
let mut len = None;
// minLength --
if let Some(min) = s.min_length {
let len = len.get_or_insert_with(|| str.chars().count());
if *len < min {
self.add_error(kind!(MinLength, *len, min));
}
}
// maxLength --
if let Some(max) = s.max_length {
let len = len.get_or_insert_with(|| str.chars().count());
if *len > max {
self.add_error(kind!(MaxLength, *len, max));
}
}
// pattern --
if let Some(regex) = &s.pattern {
if !regex.is_match(str) {
self.add_error(kind!(Pattern, str.into(), regex.as_str()));
}
}
if s.draft_version == 6 {
return;
}
// contentEncoding --
let mut decoded = Some(Cow::from(str.as_bytes()));
if let Some(decoder) = &s.content_encoding {
match (decoder.func)(str) {
Ok(bytes) => decoded = Some(Cow::from(bytes)),
Err(err) => {
decoded = None;
self.add_error(ErrorKind::ContentEncoding {
want: decoder.name,
err,
})
}
}
}
// contentMediaType --
let mut deserialized = None;
if let (Some(mt), Some(decoded)) = (&s.content_media_type, decoded) {
match (mt.func)(decoded.as_ref(), s.content_schema.is_some()) {
Ok(des) => deserialized = des,
Err(e) => {
self.add_error(kind!(ContentMediaType, decoded.into(), mt.name, e));
}
}
}
// contentSchema --
if let (Some(sch), Some(v)) = (s.content_schema, deserialized) {
if let Err(mut e) = self.schemas.validate(&v, sch, Some(self.options)) {
e.schema_url = &s.loc;
e.kind = kind!(ContentSchema);
self.errors.push(e.clone_static());
}
}
}
fn num_validate(&mut self, num: &'v Number) {
let s = self.schema;
// minimum --
if let Some(min) = &s.minimum {
if let (Some(minf), Some(numf)) = (min.as_f64(), num.as_f64()) {
if numf < minf {
self.add_error(kind!(Minimum, Cow::Borrowed(num), min));
}
}
}
// maximum --
if let Some(max) = &s.maximum {
if let (Some(maxf), Some(numf)) = (max.as_f64(), num.as_f64()) {
if numf > maxf {
self.add_error(kind!(Maximum, Cow::Borrowed(num), max));
}
}
}
// exclusiveMinimum --
if let Some(ex_min) = &s.exclusive_minimum {
if let (Some(ex_minf), Some(numf)) = (ex_min.as_f64(), num.as_f64()) {
if numf <= ex_minf {
self.add_error(kind!(ExclusiveMinimum, Cow::Borrowed(num), ex_min));
}
}
}
// exclusiveMaximum --
if let Some(ex_max) = &s.exclusive_maximum {
if let (Some(ex_maxf), Some(numf)) = (ex_max.as_f64(), num.as_f64()) {
if numf >= ex_maxf {
self.add_error(kind!(ExclusiveMaximum, Cow::Borrowed(num), ex_max));
}
}
}
// multipleOf --
if let Some(mul) = &s.multiple_of {
if let (Some(mulf), Some(numf)) = (mul.as_f64(), num.as_f64()) {
if (numf / mulf).fract() != 0.0 {
self.add_error(kind!(MultipleOf, Cow::Borrowed(num), mul));
}
}
}
}
}
// references validation
impl<'v, 's> Validator<'v, 's, '_, '_> {
fn refs_validate(&mut self) {
let s = self.schema;
macro_rules! add_err {
($result:expr) => {
if let Err(e) = $result {
self.errors.push(e);
}
};
}
// $recursiveRef --
if let Some(mut sch) = s.recursive_ref {
if self.schemas.get(sch).recursive_anchor {
sch = self.resolve_recursive_anchor(sch);
}
add_err!(self.validate_ref(sch, "$recursiveRef"));
}
// $dynamicRef --
if let Some(dref) = &s.dynamic_ref {
let mut sch = dref.sch; // initial target
if let Some(anchor) = &dref.anchor {
// $dynamicRef includes anchor
if self.schemas.get(sch).dynamic_anchor == dref.anchor {
// initial target has matching $dynamicAnchor
sch = self.resolve_dynamic_anchor(anchor, sch);
}
}
add_err!(self.validate_ref(sch, "$dynamicRef"));
}
}
fn validate_ref(
&mut self,
sch: SchemaIndex,
kw: &'static str,
) -> Result<(), ValidationError<'s, 'v>> {
if let Err(err) = self._validate_self(sch, kw.into(), false) {
let url = &self.schemas.get(sch).loc;
let mut ref_err = self.error(ErrorKind::Reference { kw, url });
if let ErrorKind::Group = err.kind {
ref_err.causes = err.causes;
} else {
ref_err.causes.push(err);
}
return Err(ref_err);
}
Ok(())
}
fn resolve_recursive_anchor(&self, fallback: SchemaIndex) -> SchemaIndex {
let mut sch = fallback;
let mut scope = &self.scope;
loop {
let scope_sch = self.schemas.get(scope.sch);
let base_sch = self.schemas.get(scope_sch.resource);
if base_sch.recursive_anchor {
sch = scope.sch
}
if let Some(parent) = scope.parent {
scope = parent;
} else {
return sch;
}
}
}
fn resolve_dynamic_anchor(&self, name: &String, fallback: SchemaIndex) -> SchemaIndex {
let mut sch = fallback;
let mut scope = &self.scope;
loop {
let scope_sch = self.schemas.get(scope.sch);
let base_sch = self.schemas.get(scope_sch.resource);
debug_assert_eq!(base_sch.idx, base_sch.resource);
if let Some(dsch) = base_sch.dynamic_anchors.get(name) {
sch = *dsch
}
if let Some(parent) = scope.parent {
scope = parent;
} else {
return sch;
}
}
}
}
// conditional validation
impl Validator<'_, '_, '_, '_> {
fn cond_validate(&mut self) {
let s = self.schema;
macro_rules! add_err {
($result:expr) => {
if let Err(e) = $result {
self.errors.push(e);
}
};
}
// not --
if let Some(not) = s.not {
if self._validate_self(not, None, true).is_ok() {
self.add_error(kind!(Not));
}
}
// allOf --
if !s.all_of.is_empty() {
let mut errors = vec![];
for sch in &s.all_of {
if let Err(e) = self.validate_self(*sch) {
errors.push(e);
if self.bool_result {
break;
}
}
}
if !errors.is_empty() {
self.add_errors(errors, kind!(AllOf));
}
}
// anyOf --
if !s.any_of.is_empty() {
let mut matched = false;
let mut errors = vec![];
for sch in &s.any_of {
match self.validate_self(*sch) {
Ok(_) => {
matched = true;
// for uneval, all schemas must be checked
if self.uneval.is_empty() {
break;
}
}
Err(e) => errors.push(e),
}
}
if !matched {
self.add_errors(errors, kind!(AnyOf));
}
}
// oneOf --
if !s.one_of.is_empty() {
let mut matched = None;
let mut errors = vec![];
for (i, sch) in s.one_of.iter().enumerate() {
if let Err(e) = self._validate_self(*sch, None, matched.is_some()) {
if matched.is_none() {
errors.push(e);
}
} else {
match matched {
None => _ = matched.replace(i),
Some(prev) => {
self.add_error(ErrorKind::OneOf(Some((prev, i))));
break;
}
}
}
}
if matched.is_none() {
self.add_errors(errors, ErrorKind::OneOf(None));
}
}
// if, then, else --
if let Some(if_) = s.if_ {
if self._validate_self(if_, None, true).is_ok() {
if let Some(then) = s.then {
add_err!(self.validate_self(then));
}
} else if let Some(else_) = s.else_ {
add_err!(self.validate_self(else_));
}
}
}
}
// uneval validation
impl Validator<'_, '_, '_, '_> {
fn uneval_validate(&mut self) {
let s = self.schema;
let v = self.v;
macro_rules! add_err {
($result:expr) => {
if let Err(e) = $result {
self.errors.push(e);
}
};
}
// unevaluatedProperties --
if let Value::Object(obj) = v {
if let Some(sch_idx) = s.unevaluated_properties {
let sch = self.schemas.get(sch_idx);
if sch.boolean == Some(false) {
// This is `unevaluatedProperties: false`, treat as additional properties
if !self.uneval.props.is_empty() {
let props: Vec<Cow<str>> =
self.uneval.props.iter().map(|p| Cow::from((*p).as_str())).collect();
self.add_error(ErrorKind::AdditionalProperties { got: props });
}
self.uneval.props.clear();
} else {
// It's a schema, validate against it
let uneval = std::mem::take(&mut self.uneval);
for pname in &uneval.props {
if let Some(pvalue) = obj.get(*pname) {
add_err!(self.validate_val(sch_idx, pvalue, prop!(pname)));
}
}
self.uneval.props.clear();
}
} else if self.options.be_strict && !self.bool_result {
// 2. Runtime strictness check
if !self.uneval.props.is_empty() {
let props: Vec<Cow<str>> = self.uneval.props.iter().map(|p| Cow::from((*p).as_str())).collect();
self.add_error(ErrorKind::AdditionalProperties { got: props });
}
self.uneval.props.clear();
}
}
// unevaluatedItems --
if let (Some(sch), Value::Array(arr)) = (s.unevaluated_items, v) {
let uneval = std::mem::take(&mut self.uneval);
for i in &uneval.items {
if let Some(pvalue) = arr.get(*i) {
add_err!(self.validate_val(sch, pvalue, item!(*i)));
}
}
self.uneval.items.clear();
}
}
}
// validation helpers
impl<'v, 's> Validator<'v, 's, '_, '_> {
fn validate_val(
&mut self,
sch: SchemaIndex,
v: &'v Value,
token: InstanceToken<'v>,
) -> Result<(), ValidationError<'s, 'v>> {
if self.vloc.len() == self.scope.vid {
self.vloc.push(token);
} else {
self.vloc[self.scope.vid] = token;
}
let scope = self.scope.child(sch, None, self.scope.vid + 1);
let schema = &self.schemas.get(sch);
// Check if the new schema turns off strictness
let allows_unevaluated = schema.boolean == Some(true) ||
if let Some(idx) = schema.unevaluated_properties {
self.schemas.get(idx).boolean == Some(true)
} else {
false
};
let mut new_options = self.options;
if allows_unevaluated {
new_options.be_strict = false;
}
let mut overrides = Override::default();
for pname in &schema.override_properties {
overrides.0.insert(pname.as_str());
}
let (result, _reply) = Validator {
v,
vloc: self.vloc,
schema,
schemas: self.schemas,
scope,
options: new_options,
overrides,
uneval: Uneval::from(v, schema, new_options.be_strict || !self.uneval.is_empty()),
errors: vec![],
bool_result: self.bool_result,
}
.validate();
// self.uneval.merge(&reply, None); // DO NOT MERGE, see https://github.com/santhosh-tekuri/boon/issues/33
result
}
fn _validate_self(
&mut self,
sch: SchemaIndex,
ref_kw: Option<&'static str>,
bool_result: bool,
) -> Result<(), ValidationError<'s, 'v>> {
let scope = self.scope.child(sch, ref_kw, self.scope.vid);
let schema = &self.schemas.get(sch);
// Check if the new schema turns off strictness
let allows_unevaluated = schema.boolean == Some(true) ||
if let Some(idx) = schema.unevaluated_properties {
self.schemas.get(idx).boolean == Some(true)
} else {
false
};
let mut new_options = self.options;
if allows_unevaluated {
new_options.be_strict = false;
}
let mut overrides = self.overrides.clone();
for pname in &self.schema.override_properties {
overrides.0.insert(pname.as_str());
}
let (result, reply) = Validator {
v: self.v,
vloc: self.vloc,
schema,
schemas: self.schemas,
scope,
options: new_options,
overrides,
uneval: self.uneval.clone(),
errors: vec![],
bool_result: self.bool_result || bool_result,
}
.validate();
self.uneval.merge(&reply, ref_kw);
result
}
#[inline(always)]
fn validate_self(&mut self, sch: SchemaIndex) -> Result<(), ValidationError<'s, 'v>> {
self._validate_self(sch, None, false)
}
}
// error helpers
impl<'v, 's> Validator<'v, 's, '_, '_> {
#[inline(always)]
fn error(&self, kind: ErrorKind<'s, 'v>) -> ValidationError<'s, 'v> {
if self.bool_result {
return ValidationError {
schema_url: &self.schema.loc,
instance_location: InstanceLocation::new(),
kind: ErrorKind::Group,
causes: vec![],
};
}
ValidationError {
schema_url: &self.schema.loc,
instance_location: self.instance_location(),
kind,
causes: vec![],
}
}
#[inline(always)]
fn add_error(&mut self, kind: ErrorKind<'s, 'v>) {
self.errors.push(self.error(kind));
}
#[inline(always)]
fn add_errors(&mut self, errors: Vec<ValidationError<'s, 'v>>, kind: ErrorKind<'s, 'v>) {
if errors.len() == 1 {
self.errors.extend(errors);
} else {
let mut err = self.error(kind);
err.causes = errors;
self.errors.push(err);
}
}
fn kw_loc(&self, mut scope: &Scope) -> String {
let mut loc = String::new();
while let Some(parent) = scope.parent {
if let Some(kw) = scope.ref_kw {
loc.insert_str(0, kw);
loc.insert(0, '/');
} else {
let cur = &self.schemas.get(scope.sch).loc;
let parent = &self.schemas.get(parent.sch).loc;
loc.insert_str(0, &cur[parent.len()..]);
}
scope = parent;
}
loc
}
fn find_missing(
&self,
obj: &'v Map<String, Value>,
required: &'s [String],
) -> Option<Vec<&'s str>> {
let mut missing = required
.iter()
.filter(|p| !obj.contains_key(p.as_str()))
.map(|p| p.as_str());
if self.bool_result {
missing.next().map(|_| Vec::new())
} else {
let missing = missing.collect::<Vec<_>>();
if missing.is_empty() {
None
} else {
Some(missing)
}
}
}
fn instance_location(&self) -> InstanceLocation<'v> {
let len = self.scope.vid;
let mut tokens = Vec::with_capacity(len);
for tok in &self.vloc[..len] {
tokens.push(tok.clone());
}
InstanceLocation { tokens }
}
}
// Uneval --
#[derive(Default, Clone)]
struct Uneval<'v> {
props: HashSet<&'v String>,
items: HashSet<usize>,
}
impl<'v> Uneval<'v> {
fn is_empty(&self) -> bool {
self.props.is_empty() && self.items.is_empty()
}
fn from(v: &'v Value, sch: &Schema, caller_needs: bool) -> Self {
let mut uneval = Self::default();
match v {
Value::Object(obj) => {
if caller_needs || sch.unevaluated_properties.is_some() || !sch.all_props_evaluated {
uneval.props = obj.keys().collect();
}
}
Value::Array(arr) => {
if !sch.all_items_evaluated
&& (caller_needs || sch.unevaluated_items.is_some())
&& sch.num_items_evaluated < arr.len()
{
uneval.items = (sch.num_items_evaluated..arr.len()).collect();
}
}
_ => (),
}
uneval
}
fn merge(&mut self, other: &Uneval<'v>, _ref_kw: Option<&'static str>) {
self.props.retain(|p| other.props.contains(p));
self.items.retain(|i| other.items.contains(i));
}
}
// Scope ---
#[derive(Debug)]
struct Scope<'a> {
sch: SchemaIndex,
// if None, compute from self.sch and self.parent.sh
// not None only when there is jump i.e $ref, $XXXRef
ref_kw: Option<&'static str>,
/// unique id of value being validated
// if two scope validate same value, they will have same vid
vid: usize,
parent: Option<&'a Scope<'a>>,
}
impl Scope<'_> {
fn child<'x>(
&'x self,
sch: SchemaIndex,
ref_kw: Option<&'static str>,
vid: usize,
) -> Scope<'x> {
Scope {
sch,
ref_kw,
vid,
parent: Some(self),
}
}
fn check_cycle(&self) -> Option<&Scope<'_>> {
let mut scope = self.parent;
while let Some(scp) = scope {
if scp.vid != self.vid {
break;
}
if scp.sch == self.sch {
return Some(scp);
}
scope = scp.parent;
}
None
}
}
/// Token in InstanceLocation json-pointer.
#[derive(Debug, Clone)]
pub enum InstanceToken<'v> {
/// Token for property.
Prop(Cow<'v, str>),
/// Token for array item.
Item(usize),
}
impl From<String> for InstanceToken<'_> {
fn from(prop: String) -> Self {
InstanceToken::Prop(prop.into())
}
}
impl<'v> From<&'v str> for InstanceToken<'v> {
fn from(prop: &'v str) -> Self {
InstanceToken::Prop(prop.into())
}
}
impl From<usize> for InstanceToken<'_> {
fn from(index: usize) -> Self {
InstanceToken::Item(index)
}
}
/// The location of the JSON value within the instance being validated
#[derive(Debug, Default)]
pub struct InstanceLocation<'v> {
pub tokens: Vec<InstanceToken<'v>>,
}
impl InstanceLocation<'_> {
fn new() -> Self {
Self::default()
}
fn clone_static(self) -> InstanceLocation<'static> {
let mut tokens = Vec::with_capacity(self.tokens.len());
for tok in self.tokens {
let tok = match tok {
InstanceToken::Prop(p) => InstanceToken::Prop(p.into_owned().into()),
InstanceToken::Item(i) => InstanceToken::Item(i),
};
tokens.push(tok);
}
InstanceLocation { tokens }
}
}
impl Display for InstanceLocation<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for tok in &self.tokens {
f.write_char('/')?;
match tok {
InstanceToken::Prop(s) => f.write_str(&escape(s))?,
InstanceToken::Item(i) => write!(f, "{i}")?,
}
}
Ok(())
}
}
impl<'s> ValidationError<'s, '_> {
pub(crate) fn clone_static(self) -> ValidationError<'s, 'static> {
let mut causes = Vec::with_capacity(self.causes.len());
for cause in self.causes {
causes.push(cause.clone_static());
}
ValidationError {
instance_location: self.instance_location.clone_static(),
kind: self.kind.clone_static(),
causes,
..self
}
}
}
impl<'s> ErrorKind<'s, '_> {
fn clone_static(self) -> ErrorKind<'s, 'static> {
use ErrorKind::*;
match self {
AdditionalProperties { got } => AdditionalProperties {
got: got.into_iter().map(|e| e.into_owned().into()).collect(),
},
Format { got, want, err } => Format {
got: Cow::Owned(got.into_owned()),
want,
err,
},
Pattern { got, want } => Pattern {
got: got.into_owned().into(),
want,
},
Minimum { got, want } => Minimum {
got: Cow::Owned(got.into_owned()),
want,
},
Maximum { got, want } => Maximum {
got: Cow::Owned(got.into_owned()),
want,
},
ExclusiveMinimum { got, want } => ExclusiveMinimum {
got: Cow::Owned(got.into_owned()),
want,
},
ExclusiveMaximum { got, want } => ExclusiveMaximum {
got: Cow::Owned(got.into_owned()),
want,
},
MultipleOf { got, want } => MultipleOf {
got: Cow::Owned(got.into_owned()),
want,
},
// #[cfg(not(debug_assertions))]
// _ => unsafe { std::mem::transmute(self) },
Group => Group,
Schema { url } => Schema { url },
ContentSchema => ContentSchema,
PropertyName { prop } => PropertyName { prop },
Reference { kw, url } => Reference { kw, url },
RefCycle {
url,
kw_loc1,
kw_loc2,
} => RefCycle {
url,
kw_loc1,
kw_loc2,
},
FalseSchema => FalseSchema,
Type { got, want } => Type { got, want },
Enum { want } => Enum { want },
Const { want } => Const { want },
MinProperties { got, want } => MinProperties { got, want },
MaxProperties { got, want } => MaxProperties { got, want },
Required { want } => Required { want },
Dependency { prop, missing } => Dependency { prop, missing },
DependentRequired { prop, missing } => DependentRequired { prop, missing },
MinItems { got, want } => MinItems { got, want },
MaxItems { got, want } => MaxItems { got, want },
Contains => Contains,
MinContains { got, want } => MinContains { got, want },
MaxContains { got, want } => MaxContains { got, want },
UniqueItems { got } => UniqueItems { got },
AdditionalItems { got } => AdditionalItems { got },
MinLength { got, want } => MinLength { got, want },
MaxLength { got, want } => MaxLength { got, want },
ContentEncoding { want, err } => ContentEncoding { want, err },
ContentMediaType { got, want, err } => ContentMediaType { got, want, err },
Not => Not,
AllOf => AllOf,
AnyOf => AnyOf,
OneOf(opt) => OneOf(opt),
}
}
}