chore: Add a few keywords

This commit is contained in:
Dmitry Dygalo 2022-05-07 22:12:10 +02:00 committed by Dmitry Dygalo
parent 53acb03304
commit 125b8f75a8
11 changed files with 276 additions and 9 deletions

View File

@ -187,6 +187,18 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "enum_dispatch"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eb359f1476bf611266ac1f5355bc14aeca37b299d0ebccc038ee7058891c9cb"
dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "form_urlencoded"
version = "1.0.1"
@ -259,6 +271,8 @@ version = "0.1.0"
dependencies = [
"bench_helpers",
"criterion",
"enum_dispatch",
"num-cmp",
"serde_json",
"test-case",
"url",
@ -306,6 +320,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num-cmp"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa"
[[package]]
name = "num-traits"
version = "0.2.15"
@ -325,6 +345,12 @@ dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]]
name = "oorandom"
version = "11.1.3"

View File

@ -4,6 +4,8 @@ version = "0.1.0"
edition = "2021"
[dependencies]
enum_dispatch = "0.3.8"
num-cmp = "0.1.0"
serde_json = "1.0.79"
url = "2.2.2"

View File

@ -7,6 +7,7 @@
use serde_json::Value;
pub mod resolver;
use crate::vocabularies::Keyword;
use resolver::LocalResolver;
pub fn build(schema: &Value) {
@ -22,15 +23,17 @@ pub fn build(schema: &Value) {
//
// Remote schemas could also have references that should be resolved, therefore
// this step is applied recursively to all resolved schemas.
let mut output = vec![Node::Value(schema)];
//
// Non-leaf nodes store their edges as a range that points to the same vector
let mut nodes = vec![];
let resolver = LocalResolver::new(schema);
build_one(schema, &resolver, &mut output);
build_one(schema, &resolver, &mut nodes);
}
pub(crate) fn build_one<'schema>(
schema: &'schema Value,
resolver: &'schema LocalResolver,
graph: &mut Vec<Node<'schema>>,
nodes: &mut Vec<Node<'schema>>,
) {
let mut stack = vec![schema];
while let Some(node) = stack.pop() {
@ -44,10 +47,10 @@ pub(crate) fn build_one<'schema>(
let resolved = resolver.resolve(value.as_str().unwrap()).unwrap();
// Do not push onto the stack, because the reference is local, therefore
// it will be processed in any case
graph.push(Node::Reference(resolved));
nodes.push(Node::Reference(resolved));
} else {
stack.push(value);
graph.push(Node::Value(value));
// local.push(Node::Keyword(value));
}
}
}
@ -59,14 +62,14 @@ pub(crate) fn build_one<'schema>(
#[derive(Debug)]
pub(crate) enum Node<'schema> {
Value(&'schema Value),
Keyword(&'schema Value),
Reference(&'schema Value),
}
impl<'schema> Node<'schema> {
pub(crate) fn as_inner(&self) -> &'schema Value {
match self {
Node::Value(value) => value,
Node::Keyword(value) => value,
Node::Reference(value) => value,
}
}
@ -81,8 +84,10 @@ mod tests {
fn it_works() {
let schema = json!({
"properties": {
"foo": {"$ref": "#"}
}
"foo": {"$ref": "#"},
"bar": {"maximum": 5}
},
"type": "object"
});
build(&schema);
}

View File

@ -1,3 +1,4 @@
mod compilation;
mod vocabularies;
pub use compilation::{build, resolver};

View File

@ -0,0 +1,46 @@
use crate::{
compilation,
vocabularies::{Keyword, Validate},
};
use serde_json::{Map, Value};
use std::ops::Range;
#[derive(Debug)]
pub struct ItemsArray {
items: Range<usize>,
}
impl Validate for ItemsArray {
fn is_valid(&self, keywords: &[Keyword], instance: &serde_json::Value) -> bool {
if let Value::Array(items) = instance {
items
.iter()
.zip(&keywords[self.items.clone()])
.all(|(item, val)| val.is_valid(keywords, item))
} else {
true
}
}
}
pub(crate) fn compile(
_: &Map<String, Value>,
schema: &serde_json::Value,
global: &mut Vec<Keyword>,
) -> Keyword {
todo!()
// match schema {
// Value::Array(schemas) => {
// let mut local = Vec::with_capacity(schemas.len());
// compilation::compile_many(schemas, global, &mut local, context);
// ItemsArray {
// items: compilation::append(global, local),
// }
// .into()
// }
// Value::Object(_) => {
// todo!()
// }
// _ => panic!(),
// }
}

View File

@ -0,0 +1,2 @@
pub(crate) mod items;
pub(crate) mod properties;

View File

@ -0,0 +1,88 @@
use crate::compilation;
// use crate::compilation::context::CompilationContext;
use crate::compilation::Node;
use crate::resolver::LocalResolver;
use crate::vocabularies::{Keyword, Validate};
use serde_json::Value;
use std::cmp::Ordering;
#[derive(Debug)]
pub struct Properties {
properties: Box<[Box<str>]>,
start: usize,
}
impl Properties {
// #[inline]
// pub(crate) fn compile<'schema>(
// schema: &'schema Value,
// global: &mut Vec<Node<'schema>>,
// resolver: &'schema LocalResolver<'schema>,
// ) -> Node<'schema> {
// match schema {
// Value::Object(map) => {
// let mut properties = Vec::with_capacity(map.len());
// let mut local = Vec::with_capacity(map.len());
// for (key, subschema) in map {
// properties.push(key.clone().into_boxed_str());
// compilation::build_one(subschema, resolver, global, &mut local)
// }
// let start = global.len();
// // global.extend(local.into_iter());
// Node::Value(schema)
// }
// _ => todo!(),
// }
// }
}
macro_rules! next {
($iter:expr) => {{
if let Some(value) = $iter.next() {
value
} else {
return true;
}
}};
}
impl Validate for Properties {
fn is_valid(&self, keywords: &[Keyword], instance: &Value) -> bool {
if let Value::Object(items) = instance {
// TODO. Separate keyword for single property
// TODO. It depends on serde feature - won't work for index map
let mut items = items.iter();
let (mut key, mut value) = next!(items);
let mut properties = self
.properties
.iter()
.zip(&keywords[self.start..self.properties.len()]);
let (mut property, mut keyword) = next!(properties);
loop {
match key.as_str().cmp(&**property) {
Ordering::Less => (key, value) = next!(items),
Ordering::Equal => {
if !keyword.is_valid(keywords, value) {
return false;
}
(key, value) = next!(items);
(property, keyword) = next!(properties);
}
Ordering::Greater => (property, keyword) = next!(properties),
}
}
} else {
true
}
}
}
//
// #[inline]
// pub(crate) fn compile<'schema>(
// schema: &'schema Value,
// global: &mut Vec<Node<'schema>>,
// resolver: &'schema LocalResolver<'schema>,
// // context: &mut CompilationContext,
// ) -> Node<'schema> {
// Properties::compile(schema, global, resolver)
// }

View File

@ -0,0 +1,23 @@
pub(crate) mod applicator;
pub(crate) mod ref_;
pub(crate) mod validation;
use enum_dispatch::enum_dispatch;
pub(crate) use applicator::{items::ItemsArray, properties::Properties};
pub(crate) use ref_::Ref;
pub(crate) use validation::maximum::Maximum;
#[enum_dispatch]
pub trait Validate {
fn is_valid(&self, keywords: &[Keyword], instance: &serde_json::Value) -> bool;
}
#[enum_dispatch(Validate)]
#[derive(Debug)]
pub enum Keyword {
ItemsArray,
Maximum,
Properties,
Ref,
}

View File

@ -0,0 +1,38 @@
use crate::vocabularies::{Keyword, Validate};
use std::ops::Range;
const EMPTY_RANGE: Range<usize> = usize::MAX..usize::MAX;
#[derive(Debug)]
pub struct Ref {
pub(crate) reference: String,
pub(crate) range: Range<usize>,
}
impl Validate for Ref {
fn is_valid(&self, keywords: &[Keyword], instance: &serde_json::Value) -> bool {
keywords[self.range.clone()]
.iter()
.all(|keyword| keyword.is_valid(keywords, instance))
}
}
pub(crate) fn compile(
schema: &serde_json::Value,
reference: String,
global: &mut [Keyword],
) -> Keyword {
// Reference
// - `#` - should point to the root scope. not yet evaluated
// - `#/other/place` - how to find it?
// - already in `global` - can I find it? should I?
// - not in `global`
// - `https://whatever.com/schema.json#/something` - ???
// Ideas:
// - Collect all references separately? then
Ref {
reference,
range: EMPTY_RANGE,
}
.into()
}

View File

@ -0,0 +1,35 @@
use crate::vocabularies::{Keyword, Validate};
use num_cmp::NumCmp;
use serde_json::Value;
#[derive(Debug)]
pub struct Maximum {
limit: u64,
}
impl Maximum {
pub fn compile(limit: u64) -> Keyword {
Self { limit }.into()
}
}
impl Validate for Maximum {
fn is_valid(&self, _: &[Keyword], instance: &Value) -> bool {
if let Value::Number(item) = instance {
if let Some(item) = item.as_u64() {
!NumCmp::num_gt(item, self.limit)
} else if let Some(item) = item.as_i64() {
!NumCmp::num_gt(item, self.limit)
} else {
let item = item.as_f64().expect("Always valid");
!NumCmp::num_gt(item, self.limit)
}
} else {
true
}
}
}
pub(crate) fn compile(limit: u64) -> Keyword {
Maximum::compile(limit)
}

View File

@ -0,0 +1 @@
pub(crate) mod maximum;