chore: Add a few keywords
This commit is contained in:
parent
53acb03304
commit
125b8f75a8
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
mod compilation;
|
||||
mod vocabularies;
|
||||
|
||||
pub use compilation::{build, resolver};
|
||||
|
|
|
@ -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!(),
|
||||
// }
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
pub(crate) mod items;
|
||||
pub(crate) mod properties;
|
|
@ -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)
|
||||
// }
|
|
@ -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,
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
pub(crate) mod maximum;
|
Loading…
Reference in New Issue