boon now included
This commit is contained in:
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"description": "zero fraction",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"const": 2
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "with fraction",
|
||||
"data": 2.0,
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "without fraction",
|
||||
"data": 2,
|
||||
"valid": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,26 @@
|
||||
[
|
||||
{
|
||||
"description": "guard against infinite recursion",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$defs": {
|
||||
"alice": {
|
||||
"$anchor": "alice",
|
||||
"allOf": [{"$ref": "#bob"}]
|
||||
},
|
||||
"bob": {
|
||||
"$anchor": "bob",
|
||||
"allOf": [{"$ref": "#alice"}]
|
||||
}
|
||||
},
|
||||
"$ref": "#alice"
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "infinite recursion detected",
|
||||
"data": {},
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,143 @@
|
||||
[
|
||||
{
|
||||
"description": "validation of binary-encoded media type documents with schema",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"contentMediaType": "application/json",
|
||||
"contentEncoding": "base64",
|
||||
"contentSchema": { "required": ["foo"], "properties": { "foo": { "type": "string" } } }
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a valid base64-encoded JSON document",
|
||||
"data": "eyJmb28iOiAiYmFyIn0K",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "another valid base64-encoded JSON document",
|
||||
"data": "eyJib28iOiAyMCwgImZvbyI6ICJiYXoifQ==",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "an invalid base64-encoded JSON document; validates false",
|
||||
"data": "eyJib28iOiAyMH0=",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "an empty object as a base64-encoded JSON document; validates false",
|
||||
"data": "e30=",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "an empty array as a base64-encoded JSON document",
|
||||
"data": "W10=",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "a validly-encoded invalid JSON document; validates false",
|
||||
"data": "ezp9Cg==",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "an invalid base64 string that is valid JSON; validates false",
|
||||
"data": "{}",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 100,
|
||||
"valid": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "contentSchema without contentMediaType",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"contentEncoding": "base64",
|
||||
"contentSchema": { "required": ["foo"], "properties": { "foo": { "type": "string" } } }
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a valid base64-encoded JSON document",
|
||||
"data": "eyJmb28iOiAiYmFyIn0K",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "another valid base64-encoded JSON document",
|
||||
"data": "eyJib28iOiAyMCwgImZvbyI6ICJiYXoifQ==",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "an invalid base64-encoded JSON document; validates true",
|
||||
"data": "eyJib28iOiAyMH0=",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "an empty object as a base64-encoded JSON document; validates true",
|
||||
"data": "e30=",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "an empty array as a base64-encoded JSON document",
|
||||
"data": "W10=",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "a validly-encoded invalid JSON document; validates true",
|
||||
"data": "ezp9Cg==",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "an invalid base64 string that is valid JSON; validates false",
|
||||
"data": "{}",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 100,
|
||||
"valid": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "contentSchema without contentEncoding",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"contentMediaType": "application/json",
|
||||
"contentSchema": { "required": ["foo"], "properties": { "foo": { "type": "string" } } }
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a valid JSON document",
|
||||
"data": "{\"foo\": \"bar\"}",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "another valid base64-encoded JSON document",
|
||||
"data": "{\"boo\": 20, \"foo\": \"baz\"}",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "an empty object; validates false",
|
||||
"data": "{}",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "an empty array; validates false",
|
||||
"data": "[]",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "invalid JSON document; validates false",
|
||||
"data": "[}",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 100,
|
||||
"valid": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"description": "validation of date strings",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"format": "date"
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "contains alphabets",
|
||||
"data": "yyyy-mm-dd",
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"description": "validation of duration strings",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"format": "duration"
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "more than one T",
|
||||
"data": "PT1MT1S",
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,31 @@
|
||||
[
|
||||
{
|
||||
"description": "validation of duration strings",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"format": "email"
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "non printable character",
|
||||
"data": "a\tb@gmail.com",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "tab ok if quoted",
|
||||
"data": "\"a\tb\"@gmail.com",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "quote inside quoted",
|
||||
"data": "\"a\"b\"@gmail.com",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "backslash inside quoted",
|
||||
"data": "\"a\\b\"@gmail.com",
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,23 @@
|
||||
[
|
||||
{
|
||||
"description": "validation of time strings",
|
||||
"schema": { "format": "time" },
|
||||
"tests": [
|
||||
{
|
||||
"description": "contains alphabets",
|
||||
"data": "ab:cd:efZ",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "no digit in second fraction",
|
||||
"data": "23:20:50.Z",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "alphabets in offset",
|
||||
"data": "08:30:06+ab:cd",
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,26 @@
|
||||
[
|
||||
{
|
||||
"description": "special characters",
|
||||
"schema": {
|
||||
"properties": {
|
||||
"a%20b/c": { "type": "number" }
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid",
|
||||
"data": {
|
||||
"a%20b/c": 1
|
||||
},
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "invalid",
|
||||
"data": {
|
||||
"a%20b/c": "hello"
|
||||
},
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
74
validator/tests/Extra-Test-Suite/tests/draft2020-12/ref.json
Normal file
74
validator/tests/Extra-Test-Suite/tests/draft2020-12/ref.json
Normal file
@ -0,0 +1,74 @@
|
||||
[
|
||||
{
|
||||
"description": "percent-encoded json-pointer",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$defs": {
|
||||
"a b": {"type": "number"}
|
||||
},
|
||||
"$ref": "#/$defs/a%20b"
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "match",
|
||||
"data": 1,
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "mismatch",
|
||||
"data": "foobar",
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "precent in resource ptr",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$defs": {
|
||||
"a%20b": {
|
||||
"$id": "http://temp.com/ab",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"$ref": "http://temp.com/ab"
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "match",
|
||||
"data": 1,
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "mismatch",
|
||||
"data": "foobar",
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "precent in anchor ptr",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$defs": {
|
||||
"a%20b": {
|
||||
"$anchor": "abcd",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"$ref": "#abcd"
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "match",
|
||||
"data": 1,
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "mismatch",
|
||||
"data": "foobar",
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,57 @@
|
||||
[
|
||||
{
|
||||
"description": "unevaluatedProperties with a failing $ref",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$defs": {
|
||||
"child": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"prop2": { "type": "string" }
|
||||
},
|
||||
"unevaluatedProperties": false
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"prop1": { "type": "string" },
|
||||
"child_schema": { "$ref": "#/$defs/child" }
|
||||
},
|
||||
"unevaluatedProperties": false
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "unevaluated property in child should fail validation",
|
||||
"data": {
|
||||
"prop1": "value1",
|
||||
"child_schema": {
|
||||
"prop2": "value2",
|
||||
"extra_prop_in_child": "this should fail"
|
||||
}
|
||||
},
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "a valid instance should pass",
|
||||
"data": {
|
||||
"prop1": "value1",
|
||||
"child_schema": {
|
||||
"prop2": "value2"
|
||||
}
|
||||
},
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "unevaluated property in parent should fail",
|
||||
"data": {
|
||||
"prop1": "value1",
|
||||
"child_schema": {
|
||||
"prop2": "value2"
|
||||
},
|
||||
"extra_prop_in_parent": "this should fail"
|
||||
},
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"description": "zero fraction",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"uniqueItems": true
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "with fraction",
|
||||
"data": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, 2.0],
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "without fraction",
|
||||
"data": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, 2],
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,27 @@
|
||||
[
|
||||
{
|
||||
"description": "percent in dependencies",
|
||||
"schema": {
|
||||
"dependencies": {
|
||||
"a%20b": { "required": ["x"] }
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid",
|
||||
"data": {
|
||||
"a%20b": null,
|
||||
"x": 1
|
||||
},
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "invalid",
|
||||
"data": {
|
||||
"a%20b": null
|
||||
},
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,50 @@
|
||||
[
|
||||
{
|
||||
"description": "skip then when if is false",
|
||||
"schema": {
|
||||
"if": false,
|
||||
"then": {
|
||||
"$ref": "blah/blah.json"
|
||||
},
|
||||
"else": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is valid",
|
||||
"data": 0,
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "hello",
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "skip else when if is true",
|
||||
"schema": {
|
||||
"if": true,
|
||||
"then": {
|
||||
"type": "number"
|
||||
},
|
||||
"else": {
|
||||
"$ref": "blah/blah.json"
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is valid",
|
||||
"data": 0,
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "hello",
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,98 @@
|
||||
[
|
||||
{
|
||||
"description": "validation of period",
|
||||
"schema": { "format": "period" },
|
||||
"tests": [
|
||||
{
|
||||
"description": "all string formats ignore integers",
|
||||
"data": 12,
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "all string formats ignore floats",
|
||||
"data": 13.7,
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "all string formats ignore objects",
|
||||
"data": {},
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "all string formats ignore arrays",
|
||||
"data": [],
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "all string formats ignore booleans",
|
||||
"data": false,
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "all string formats ignore nulls",
|
||||
"data": null,
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "both-explicit",
|
||||
"data": "1963-06-19T08:30:06Z/1963-06-19T08:30:07Z",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "start-explicit",
|
||||
"data": "1963-06-19T08:30:06Z/P4DT12H30M5S",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "end-explicit",
|
||||
"data": "P4DT12H30M5S/1963-06-19T08:30:06Z",
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "none-explicit",
|
||||
"data": "P4DT12H30M5S/P4DT12H30M5S",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "just date",
|
||||
"data": "1963-06-19T08:30:06Z",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "just duration",
|
||||
"data": "P4DT12H30M5S",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "more than two",
|
||||
"data": "1963-06-19T08:30:06Z/1963-06-19T08:30:07Z/1963-06-19T08:30:07Z",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "separated by space",
|
||||
"data": "1963-06-19T08:30:06Z 1963-06-19T08:30:07Z",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "separated by hyphen",
|
||||
"data": "1963-06-19T08:30:06Z-1963-06-19T08:30:07Z",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "invalid components",
|
||||
"data": "foo/bar",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "emtpy components",
|
||||
"data": "/",
|
||||
"valid": false
|
||||
},
|
||||
{
|
||||
"description": "empty string",
|
||||
"data": "",
|
||||
"valid": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
87
validator/tests/compiler.rs
Normal file
87
validator/tests/compiler.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use std::error::Error;
|
||||
|
||||
use boon::{Compiler, Schemas};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_metaschema_resource() -> Result<(), Box<dyn Error>> {
|
||||
let main_schema = json!({
|
||||
"$schema": "http://tmp.com/meta.json",
|
||||
"type": "number"
|
||||
});
|
||||
let meta_schema = json!({
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$vocabulary": {
|
||||
"https://json-schema.org/draft/2020-12/vocab/applicator": true,
|
||||
"https://json-schema.org/draft/2020-12/vocab/core": true
|
||||
},
|
||||
"allOf": [
|
||||
{ "$ref": "https://json-schema.org/draft/2020-12/meta/applicator" },
|
||||
{ "$ref": "https://json-schema.org/draft/2020-12/meta/core" }
|
||||
]
|
||||
});
|
||||
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.add_resource("schema.json", main_schema)?;
|
||||
compiler.add_resource("http://tmp.com/meta.json", meta_schema)?;
|
||||
compiler.compile("schema.json", &mut schemas)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile_anchor() -> Result<(), Box<dyn Error>> {
|
||||
let schema = json!({
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$defs": {
|
||||
"x": {
|
||||
"$anchor": "a1",
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.add_resource("schema.json", schema)?;
|
||||
let sch_index1 = compiler.compile("schema.json#a1", &mut schemas)?;
|
||||
let sch_index2 = compiler.compile("schema.json#/$defs/x", &mut schemas)?;
|
||||
assert_eq!(sch_index1, sch_index2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile_nonstd() -> Result<(), Box<dyn Error>> {
|
||||
let schema = json!({
|
||||
"components": {
|
||||
"schemas": {
|
||||
"foo" : {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$defs": {
|
||||
"x": {
|
||||
"$anchor": "a",
|
||||
"type": "number"
|
||||
},
|
||||
"y": {
|
||||
"$id": "http://temp.com/y",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{ "$ref": "#a" },
|
||||
{ "$ref": "http://temp.com/y" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.add_resource("schema.json", schema)?;
|
||||
compiler.compile("schema.json#/components/schemas/foo", &mut schemas)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
33
validator/tests/debug.json
Normal file
33
validator/tests/debug.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"remotes": {
|
||||
"http://localhost:1234/draft2020-12/locationIndependentIdentifier.json": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$defs": {
|
||||
"refToInteger": {
|
||||
"$ref": "#foo"
|
||||
},
|
||||
"A": {
|
||||
"$anchor": "foo",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"a": {
|
||||
"$ref": "http://localhost:1234/draft2020-12/locationIndependentIdentifier.json#foo"
|
||||
},
|
||||
"b": {
|
||||
"$ref": "http://localhost:1234/draft2020-12/locationIndependentIdentifier.json#foo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"a": 1,
|
||||
"b": "hello"
|
||||
},
|
||||
"valid": false
|
||||
}
|
||||
41
validator/tests/debug.rs
Normal file
41
validator/tests/debug.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use std::{error::Error, fs::File};
|
||||
|
||||
use boon::{Compiler, Schemas, UrlLoader};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
#[test]
|
||||
fn test_debug() -> Result<(), Box<dyn Error>> {
|
||||
let test: Value = serde_json::from_reader(File::open("tests/debug.json")?)?;
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.enable_format_assertions();
|
||||
compiler.enable_content_assertions();
|
||||
let remotes = Remotes(test["remotes"].as_object().unwrap().clone());
|
||||
compiler.use_loader(Box::new(remotes));
|
||||
let url = "http://debug.com/schema.json";
|
||||
compiler.add_resource(url, test["schema"].clone())?;
|
||||
let sch = compiler.compile(url, &mut schemas)?;
|
||||
let result = schemas.validate(&test["data"], sch);
|
||||
if let Err(e) = &result {
|
||||
for line in format!("{e}").lines() {
|
||||
println!(" {line}");
|
||||
}
|
||||
for line in format!("{e:#}").lines() {
|
||||
println!(" {line}");
|
||||
}
|
||||
println!("{:#}", e.detailed_output());
|
||||
}
|
||||
assert_eq!(result.is_ok(), test["valid"].as_bool().unwrap());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Remotes(Map<String, Value>);
|
||||
|
||||
impl UrlLoader for Remotes {
|
||||
fn load(&self, url: &str) -> Result<Value, Box<dyn Error>> {
|
||||
if let Some(v) = self.0.get(url) {
|
||||
return Ok(v.clone());
|
||||
}
|
||||
Err("remote not found")?
|
||||
}
|
||||
}
|
||||
230
validator/tests/examples.rs
Normal file
230
validator/tests/examples.rs
Normal file
@ -0,0 +1,230 @@
|
||||
use std::{error::Error, fs::File};
|
||||
|
||||
use boon::{Compiler, Decoder, FileLoader, Format, MediaType, Schemas, SchemeUrlLoader, UrlLoader};
|
||||
use serde::de::IgnoredAny;
|
||||
use serde_json::{json, Value};
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
fn example_from_files() -> Result<(), Box<dyn Error>> {
|
||||
let schema_file = "tests/examples/schema.json";
|
||||
let instance: Value = serde_json::from_reader(File::open("tests/examples/instance.json")?)?;
|
||||
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
let sch_index = compiler.compile(schema_file, &mut schemas)?;
|
||||
let result = schemas.validate(&instance, sch_index);
|
||||
assert!(result.is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
This example shows how to load json schema from strings.
|
||||
|
||||
The schema url used plays important role in resolving
|
||||
schema references.
|
||||
|
||||
You can see that `cat.json` is resolved internally to
|
||||
another string schema where as dog.json is resolved
|
||||
to local file.
|
||||
*/
|
||||
#[test]
|
||||
fn example_from_strings() -> Result<(), Box<dyn Error>> {
|
||||
let cat_schema: Value = json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"speak": { "const": "meow" }
|
||||
},
|
||||
"required": ["speak"]
|
||||
});
|
||||
let pet_schema: Value = json!({
|
||||
"oneOf": [
|
||||
{ "$ref": "dog.json" },
|
||||
{ "$ref": "cat.json" }
|
||||
]
|
||||
});
|
||||
let instance: Value = json!({"speak": "bow"});
|
||||
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.add_resource("tests/examples/pet.json", pet_schema)?;
|
||||
compiler.add_resource("tests/examples/cat.json", cat_schema)?;
|
||||
let sch_index = compiler.compile("tests/examples/pet.json", &mut schemas)?;
|
||||
let result = schemas.validate(&instance, sch_index);
|
||||
assert!(result.is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn example_from_https() -> Result<(), Box<dyn Error>> {
|
||||
let schema_url = "https://json-schema.org/learn/examples/geographical-location.schema.json";
|
||||
let instance: Value = json!({"latitude": 48.858093, "longitude": 2.294694});
|
||||
|
||||
struct HttpUrlLoader;
|
||||
impl UrlLoader for HttpUrlLoader {
|
||||
fn load(&self, url: &str) -> Result<Value, Box<dyn Error>> {
|
||||
let reader = ureq::get(url).call()?.into_reader();
|
||||
Ok(serde_json::from_reader(reader)?)
|
||||
}
|
||||
}
|
||||
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
let mut loader = SchemeUrlLoader::new();
|
||||
loader.register("file", Box::new(FileLoader));
|
||||
loader.register("http", Box::new(HttpUrlLoader));
|
||||
loader.register("https", Box::new(HttpUrlLoader));
|
||||
compiler.use_loader(Box::new(loader));
|
||||
let sch_index = compiler.compile(schema_url, &mut schemas)?;
|
||||
let result = schemas.validate(&instance, sch_index);
|
||||
assert!(result.is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_from_yaml_files() -> Result<(), Box<dyn Error>> {
|
||||
let schema_file = "tests/examples/schema.yml";
|
||||
let instance: Value = serde_yaml::from_reader(File::open("tests/examples/instance.yml")?)?;
|
||||
|
||||
struct FileUrlLoader;
|
||||
impl UrlLoader for FileUrlLoader {
|
||||
fn load(&self, url: &str) -> Result<Value, Box<dyn Error>> {
|
||||
let url = Url::parse(url)?;
|
||||
let path = url.to_file_path().map_err(|_| "invalid file path")?;
|
||||
let file = File::open(&path)?;
|
||||
if path
|
||||
.extension()
|
||||
.filter(|&ext| ext == "yaml" || ext == "yml")
|
||||
.is_some()
|
||||
{
|
||||
Ok(serde_yaml::from_reader(file)?)
|
||||
} else {
|
||||
Ok(serde_json::from_reader(file)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
let mut loader = SchemeUrlLoader::new();
|
||||
loader.register("file", Box::new(FileUrlLoader));
|
||||
compiler.use_loader(Box::new(loader));
|
||||
let sch_index = compiler.compile(schema_file, &mut schemas)?;
|
||||
let result = schemas.validate(&instance, sch_index);
|
||||
assert!(result.is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_custom_format() -> Result<(), Box<dyn Error>> {
|
||||
let schema_url = "http://tmp/schema.json";
|
||||
let schema: Value = json!({"type": "string", "format": "palindrome"});
|
||||
let instance: Value = json!("step on no pets");
|
||||
|
||||
fn is_palindrome(v: &Value) -> Result<(), Box<dyn Error>> {
|
||||
let Value::String(s) = v else {
|
||||
return Ok(()); // applicable only on strings
|
||||
};
|
||||
let mut chars = s.chars();
|
||||
while let (Some(c1), Some(c2)) = (chars.next(), chars.next_back()) {
|
||||
if c1 != c2 {
|
||||
Err("char mismatch")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.enable_format_assertions(); // in draft2020-12 format assertions are not enabled by default
|
||||
compiler.register_format(Format {
|
||||
name: "palindrome",
|
||||
func: is_palindrome,
|
||||
});
|
||||
compiler.add_resource(schema_url, schema)?;
|
||||
let sch_index = compiler.compile(schema_url, &mut schemas)?;
|
||||
let result = schemas.validate(&instance, sch_index);
|
||||
assert!(result.is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_custom_content_encoding() -> Result<(), Box<dyn Error>> {
|
||||
let schema_url = "http://tmp/schema.json";
|
||||
let schema: Value = json!({"type": "string", "contentEncoding": "hex"});
|
||||
let instance: Value = json!("aBcdxyz");
|
||||
|
||||
fn decode(b: u8) -> Result<u8, Box<dyn Error>> {
|
||||
match b {
|
||||
b'0'..=b'9' => Ok(b - b'0'),
|
||||
b'a'..=b'f' => Ok(b - b'a' + 10),
|
||||
b'A'..=b'F' => Ok(b - b'A' + 10),
|
||||
_ => Err("decode_hex: non-hex char")?,
|
||||
}
|
||||
}
|
||||
fn decode_hex(s: &str) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
if s.len() % 2 != 0 {
|
||||
Err("decode_hex: odd length")?;
|
||||
}
|
||||
let mut bytes = s.bytes();
|
||||
let mut out = Vec::with_capacity(s.len() / 2);
|
||||
for _ in 0..out.len() {
|
||||
if let (Some(b1), Some(b2)) = (bytes.next(), bytes.next()) {
|
||||
out.push(decode(b1)? << 4 | decode(b2)?);
|
||||
} else {
|
||||
Err("decode_hex: non-ascii char")?;
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.enable_content_assertions(); // content assertions are not enabled by default
|
||||
compiler.register_content_encoding(Decoder {
|
||||
name: "hex",
|
||||
func: decode_hex,
|
||||
});
|
||||
compiler.add_resource(schema_url, schema)?;
|
||||
let sch_index = compiler.compile(schema_url, &mut schemas)?;
|
||||
let result = schemas.validate(&instance, sch_index);
|
||||
assert!(result.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_custom_content_media_type() -> Result<(), Box<dyn Error>> {
|
||||
let schema_url = "http://tmp/schema.json";
|
||||
let schema: Value = json!({"type": "string", "contentMediaType": "application/yaml"});
|
||||
let instance: Value = json!("name:foobar");
|
||||
|
||||
fn check_yaml(bytes: &[u8], deserialize: bool) -> Result<Option<Value>, Box<dyn Error>> {
|
||||
if deserialize {
|
||||
return Ok(Some(serde_yaml::from_slice(bytes)?));
|
||||
}
|
||||
serde_yaml::from_slice::<IgnoredAny>(bytes)?;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.enable_content_assertions(); // content assertions are not enabled by default
|
||||
compiler.register_content_media_type(MediaType {
|
||||
name: "application/yaml",
|
||||
json_compatible: true,
|
||||
func: check_yaml,
|
||||
});
|
||||
compiler.add_resource(schema_url, schema)?;
|
||||
let sch_index = compiler.compile(schema_url, &mut schemas)?;
|
||||
let result = schemas.validate(&instance, sch_index);
|
||||
assert!(result.is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
7
validator/tests/examples/dog.json
Normal file
7
validator/tests/examples/dog.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"speak": { "const": "bow" }
|
||||
},
|
||||
"required": ["speak"]
|
||||
}
|
||||
4
validator/tests/examples/instance.json
Normal file
4
validator/tests/examples/instance.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"firstName": "Santhosh Kumar",
|
||||
"lastName": "Tekuri"
|
||||
}
|
||||
2
validator/tests/examples/instance.yml
Normal file
2
validator/tests/examples/instance.yml
Normal file
@ -0,0 +1,2 @@
|
||||
firstName: Santhosh Kumar
|
||||
lastName: Tekuri
|
||||
12
validator/tests/examples/sample schema.json
Normal file
12
validator/tests/examples/sample schema.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["firstName", "lastName"]
|
||||
}
|
||||
12
validator/tests/examples/schema.json
Normal file
12
validator/tests/examples/schema.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["firstName", "lastName"]
|
||||
}
|
||||
9
validator/tests/examples/schema.yml
Normal file
9
validator/tests/examples/schema.yml
Normal file
@ -0,0 +1,9 @@
|
||||
type: object
|
||||
properties:
|
||||
firstName:
|
||||
type: string
|
||||
lastName:
|
||||
type: string
|
||||
required:
|
||||
- firstName
|
||||
- lastName
|
||||
44
validator/tests/filepaths.rs
Normal file
44
validator/tests/filepaths.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use std::fs;
|
||||
|
||||
use boon::{CompileError, Compiler, Schemas};
|
||||
|
||||
fn test(path: &str) -> Result<(), CompileError> {
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.compile(path, &mut schemas)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_absolute() -> Result<(), CompileError> {
|
||||
let path = fs::canonicalize("tests/examples/schema.json").unwrap();
|
||||
test(path.to_string_lossy().as_ref())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_slash() -> Result<(), CompileError> {
|
||||
test("tests/examples/schema.json")
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_relative_backslash() -> Result<(), CompileError> {
|
||||
test("tests\\examples\\schema.json")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_absolutei_space() -> Result<(), CompileError> {
|
||||
let path = fs::canonicalize("tests/examples/sample schema.json").unwrap();
|
||||
test(path.to_string_lossy().as_ref())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_slash_space() -> Result<(), CompileError> {
|
||||
test("tests/examples/sample schema.json")
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_relative_backslash_space() -> Result<(), CompileError> {
|
||||
test("tests\\examples\\sample schema.json")
|
||||
}
|
||||
244
validator/tests/invalid-schemas.json
Normal file
244
validator/tests/invalid-schemas.json
Normal file
@ -0,0 +1,244 @@
|
||||
[
|
||||
{
|
||||
"description": "InvalidJsonPointer",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$ref": "#/a~0b~~cd"
|
||||
},
|
||||
"errors": [
|
||||
"InvalidJsonPointer(\"http://fake.com/schema.json#/a~0b~~cd\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "UnsupportedUrlScheme",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$ref": "ftp://mars.com/schema.json"
|
||||
},
|
||||
"errors": [
|
||||
"UnsupportedUrlScheme { url: \"ftp://mars.com/schema.json\" }"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ValidationError",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"patternProperties": {
|
||||
"^(abc]": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
"ValidationError { url: \"http://fake.com/schema.json#\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ValidationError-nonsubschema",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"dummy": {
|
||||
"type": 1
|
||||
},
|
||||
"$ref": "#/dummy"
|
||||
},
|
||||
"errors": [
|
||||
"ValidationError { url: \"http://fake.com/schema.json#/dummy\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "JsonPointerNotFound-obj",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$ref": "#/$defs/something"
|
||||
},
|
||||
"errors": [
|
||||
"JsonPointerNotFound(\"http://fake.com/schema.json#/$defs/something\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "JsonPointerNotFound-arr-pos",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$ref": "#/dummy/0",
|
||||
"dummy": []
|
||||
},
|
||||
"errors": [
|
||||
"JsonPointerNotFound(\"http://fake.com/schema.json#/dummy/0\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "JsonPointerNotFound-arr-neg",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$ref": "#/dummy/-1",
|
||||
"dummy": []
|
||||
},
|
||||
"errors": [
|
||||
"JsonPointerNotFound(\"http://fake.com/schema.json#/dummy/-1\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "JsonPointerNotFound-primitive",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$ref": "#/$schema/something"
|
||||
},
|
||||
"errors": [
|
||||
"JsonPointerNotFound(\"http://fake.com/schema.json#/$schema/something\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "InvalidRegex",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft-04/schema",
|
||||
"patternProperties": {
|
||||
"^(abc]": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
"InvalidRegex { url: \"http://fake.com/schema.json#/patternProperties\", regex: \"^(abc]\", "
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "DuplicateId",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$defs": {
|
||||
"a": {
|
||||
"$id": "http://a.com/b",
|
||||
"$defs": {
|
||||
"b": {
|
||||
"$id": "a.json"
|
||||
},
|
||||
"c": {
|
||||
"$id": "a.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
"DuplicateId { url: \"http://fake.com/schema.json\", id: \"http://a.com/a.json\", ",
|
||||
"\"/$defs/a/$defs/b\"",
|
||||
"\"/$defs/a/$defs/c\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "DuplicateAnchor",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$defs": {
|
||||
"a": {
|
||||
"$id": "http://a.com/b",
|
||||
"$defs": {
|
||||
"b": {
|
||||
"$anchor": "a1"
|
||||
},
|
||||
"c": {
|
||||
"$anchor": "a1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
"DuplicateAnchor { anchor: \"a1\", url: \"http://fake.com/schema.json\", ",
|
||||
"\"/$defs/a/$defs/b\"",
|
||||
"\"/$defs/a/$defs/c\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "UnsupportedDraft",
|
||||
"remotes": {
|
||||
"http://remotes/a.json": {
|
||||
"$schema": "http://remotes/b.json"
|
||||
},
|
||||
"http://remotes/b.json": {
|
||||
"$schema": "http://remotes/b.json"
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"$schema": "http://remotes/a.json"
|
||||
},
|
||||
"errors": [
|
||||
"UnsupportedDraft { url: \"http://remotes/b.json\" }"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "MetaSchemaCycle",
|
||||
"remotes": {
|
||||
"http://remotes/a.json": {
|
||||
"$schema": "http://remotes/b.json"
|
||||
},
|
||||
"http://remotes/b.json": {
|
||||
"$schema": "http://remotes/a.json"
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"$schema": "http://remotes/a.json"
|
||||
},
|
||||
"errors": [
|
||||
"MetaSchemaCycle { url: \"http://remotes/a.json\" }"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "AnchorNotFound-local",
|
||||
"schema": {
|
||||
"$ref": "sample.json#abcd",
|
||||
"$defs": {
|
||||
"a": {
|
||||
"$id": "sample.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
"AnchorNotFound { url: \"http://fake.com/schema.json\", reference: \"http://fake.com/sample.json#abcd\" }"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "AnchorNotFound-remote",
|
||||
"remotes": {
|
||||
"http://remotes/a.json": {}
|
||||
},
|
||||
"schema": {
|
||||
"$ref": "http://remotes/a.json#abcd"
|
||||
},
|
||||
"errors": [
|
||||
"AnchorNotFound { url: \"http://remotes/a.json\", reference: \"http://remotes/a.json#abcd\" }"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "UnsupportedVocabulary-required",
|
||||
"remotes": {
|
||||
"http://remotes/a.json": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema#",
|
||||
"$vocabulary": {
|
||||
"https://json-schema.org/draft/2019-09/vocab/format": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"$schema": "http://remotes/a.json"
|
||||
},
|
||||
"errors": [
|
||||
"UnsupportedVocabulary { url: \"http://remotes/a.json\", vocabulary: \"https://json-schema.org/draft/2019-09/vocab/format\" }"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "UnsupportedVocabulary-optioanl",
|
||||
"remotes": {
|
||||
"http://remotes/a.json": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema#",
|
||||
"$vocabulary": {
|
||||
"https://json-schema.org/draft/2019-09/vocab/format": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"$schema": "http://remotes/a.json"
|
||||
}
|
||||
}
|
||||
]
|
||||
67
validator/tests/invalid-schemas.rs
Normal file
67
validator/tests/invalid-schemas.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use std::{collections::HashMap, error::Error, fs::File};
|
||||
|
||||
use boon::{CompileError, Compiler, Schemas, UrlLoader};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Test {
|
||||
description: String,
|
||||
remotes: Option<HashMap<String, Value>>,
|
||||
schema: Value,
|
||||
errors: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_schemas() -> Result<(), Box<dyn Error>> {
|
||||
let file = File::open("tests/invalid-schemas.json")?;
|
||||
let tests: Vec<Test> = serde_json::from_reader(file)?;
|
||||
for test in tests {
|
||||
println!("{}", test.description);
|
||||
match compile(&test) {
|
||||
Ok(_) => {
|
||||
if test.errors.is_some() {
|
||||
Err("want compilation to fail")?
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!(" {e}");
|
||||
let error = format!("{e:?}");
|
||||
let Some(errors) = &test.errors else {
|
||||
Err("want compilation to succeed")?
|
||||
};
|
||||
for want in errors {
|
||||
if !error.contains(want) {
|
||||
println!(" got {error}");
|
||||
println!(" want {want}");
|
||||
panic!("error mismatch");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile(test: &Test) -> Result<(), CompileError> {
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
let url = "http://fake.com/schema.json";
|
||||
if let Some(remotes) = &test.remotes {
|
||||
compiler.use_loader(Box::new(Remotes(remotes.clone())));
|
||||
}
|
||||
compiler.add_resource(url, test.schema.clone())?;
|
||||
compiler.compile(url, &mut schemas)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Remotes(HashMap<String, Value>);
|
||||
|
||||
impl UrlLoader for Remotes {
|
||||
fn load(&self, url: &str) -> Result<Value, Box<dyn Error>> {
|
||||
if let Some(v) = self.0.get(url) {
|
||||
return Ok(v.clone());
|
||||
}
|
||||
Err("remote not found")?
|
||||
}
|
||||
}
|
||||
122
validator/tests/output.rs
Normal file
122
validator/tests/output.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use std::{env, error::Error, fs::File, path::Path};
|
||||
|
||||
use boon::{Compiler, Draft, Schemas};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
#[test]
|
||||
fn test_suites() -> Result<(), Box<dyn Error>> {
|
||||
if let Ok(suite) = env::var("TEST_SUITE") {
|
||||
test_suite(&suite)?;
|
||||
} else {
|
||||
test_suite("tests/JSON-Schema-Test-Suite")?;
|
||||
test_suite("tests/Extra-Suite")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_suite(suite: &str) -> Result<(), Box<dyn Error>> {
|
||||
test_folder(suite, "draft2019-09", Draft::V2019_09)?;
|
||||
test_folder(suite, "draft2020-12", Draft::V2020_12)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_folder(suite: &str, folder: &str, draft: Draft) -> Result<(), Box<dyn Error>> {
|
||||
let output_schema_url = format!(
|
||||
"https://json-schema.org/draft/{}/output/schema",
|
||||
folder.strip_prefix("draft").unwrap()
|
||||
);
|
||||
let prefix = Path::new(suite).join("output-tests");
|
||||
let folder = prefix.join(folder);
|
||||
let content = folder.join("content");
|
||||
if !content.is_dir() {
|
||||
return Ok(());
|
||||
}
|
||||
let output_schema: Value =
|
||||
serde_json::from_reader(File::open(folder.join("output-schema.json"))?)?;
|
||||
for entry in content.read_dir()? {
|
||||
let entry = entry?;
|
||||
if !entry.file_type()?.is_file() {
|
||||
continue;
|
||||
};
|
||||
let entry_path = entry.path();
|
||||
println!("{}", entry_path.strip_prefix(&prefix)?.to_str().unwrap());
|
||||
let groups: Vec<Group> = serde_json::from_reader(File::open(entry_path)?)?;
|
||||
for group in groups {
|
||||
println!(" {}", group.description);
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.set_default_draft(draft);
|
||||
let schema_url = "http://output-tests/schema";
|
||||
compiler.add_resource(schema_url, group.schema)?;
|
||||
let sch = compiler.compile(schema_url, &mut schemas)?;
|
||||
for test in group.tests {
|
||||
println!(" {}", test.description);
|
||||
match schemas.validate(&test.data, sch) {
|
||||
Ok(_) => println!(" validation success"),
|
||||
Err(e) => {
|
||||
if let Some(sch) = test.output.basic {
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.set_default_draft(draft);
|
||||
compiler.add_resource(&output_schema_url, output_schema.clone())?;
|
||||
let schema_url = "http://output-tests/schema";
|
||||
compiler.add_resource(schema_url, sch)?;
|
||||
let sch = compiler.compile(schema_url, &mut schemas)?;
|
||||
let basic: Value = serde_json::from_str(&e.basic_output().to_string())?;
|
||||
let result = schemas.validate(&basic, sch);
|
||||
if let Err(e) = result {
|
||||
println!("{basic:#}\n");
|
||||
for line in format!("{e}").lines() {
|
||||
println!(" {line}");
|
||||
}
|
||||
panic!("basic output did not match");
|
||||
}
|
||||
}
|
||||
if let Some(sch) = test.output.detailed {
|
||||
let mut schemas = Schemas::new();
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.set_default_draft(draft);
|
||||
compiler.add_resource(&output_schema_url, output_schema.clone())?;
|
||||
let schema_url = "http://output-tests/schema";
|
||||
compiler.add_resource(schema_url, sch)?;
|
||||
let sch = compiler.compile(schema_url, &mut schemas)?;
|
||||
let detailed: Value =
|
||||
serde_json::from_str(&e.detailed_output().to_string())?;
|
||||
let result = schemas.validate(&detailed, sch);
|
||||
if let Err(e) = result {
|
||||
println!("{detailed:#}\n");
|
||||
for line in format!("{e}").lines() {
|
||||
println!(" {line}");
|
||||
}
|
||||
panic!("detailed output did not match");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Group {
|
||||
description: String,
|
||||
schema: Value,
|
||||
tests: Vec<Test>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Test {
|
||||
description: String,
|
||||
data: Value,
|
||||
output: Output,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Output {
|
||||
basic: Option<Value>,
|
||||
detailed: Option<Value>,
|
||||
}
|
||||
120
validator/tests/suite.rs
Normal file
120
validator/tests/suite.rs
Normal file
@ -0,0 +1,120 @@
|
||||
use std::{env, error::Error, ffi::OsStr, fs::File, path::Path};
|
||||
|
||||
use boon::{Compiler, Draft, Schemas, UrlLoader};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
static SKIP: [&str; 2] = [
|
||||
"zeroTerminatedFloats.json", // only draft4: this behavior is changed in later drafts
|
||||
"float-overflow.json",
|
||||
];
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Group {
|
||||
description: String,
|
||||
schema: Value,
|
||||
tests: Vec<Test>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Test {
|
||||
description: String,
|
||||
data: Value,
|
||||
valid: bool,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_suites() -> Result<(), Box<dyn Error>> {
|
||||
if let Ok(suite) = env::var("TEST_SUITE") {
|
||||
test_suite(&suite)?;
|
||||
} else {
|
||||
test_suite("tests/JSON-Schema-Test-Suite")?;
|
||||
test_suite("tests/Extra-Test-Suite")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_suite(suite: &str) -> Result<(), Box<dyn Error>> {
|
||||
if !Path::new(suite).exists() {
|
||||
Err(format!("test suite {suite} does not exist"))?;
|
||||
}
|
||||
test_dir(suite, "draft4", Draft::V4)?;
|
||||
test_dir(suite, "draft6", Draft::V6)?;
|
||||
test_dir(suite, "draft7", Draft::V7)?;
|
||||
test_dir(suite, "draft2019-09", Draft::V2019_09)?;
|
||||
test_dir(suite, "draft2020-12", Draft::V2020_12)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_dir(suite: &str, path: &str, draft: Draft) -> Result<(), Box<dyn Error>> {
|
||||
let prefix = Path::new(suite).join("tests");
|
||||
let dir = prefix.join(path);
|
||||
if !dir.is_dir() {
|
||||
return Ok(());
|
||||
}
|
||||
for entry in dir.read_dir()? {
|
||||
let entry = entry?;
|
||||
let file_type = entry.file_type()?;
|
||||
let tmp_entry_path = entry.path();
|
||||
let entry_path = tmp_entry_path.strip_prefix(&prefix)?.to_str().unwrap();
|
||||
if file_type.is_file() {
|
||||
if !SKIP.iter().any(|n| OsStr::new(n) == entry.file_name()) {
|
||||
test_file(suite, entry_path, draft)?;
|
||||
}
|
||||
} else if file_type.is_dir() {
|
||||
test_dir(suite, entry_path, draft)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_file(suite: &str, path: &str, draft: Draft) -> Result<(), Box<dyn Error>> {
|
||||
println!("FILE: {path}");
|
||||
let path = Path::new(suite).join("tests").join(path);
|
||||
let optional = path.components().any(|comp| comp.as_os_str() == "optional");
|
||||
let file = File::open(path)?;
|
||||
|
||||
let url = "http://testsuite.com/schema.json";
|
||||
let groups: Vec<Group> = serde_json::from_reader(file)?;
|
||||
for group in groups {
|
||||
println!("{}", group.description);
|
||||
let mut schemas = Schemas::default();
|
||||
let mut compiler = Compiler::default();
|
||||
compiler.set_default_draft(draft);
|
||||
if optional {
|
||||
compiler.enable_format_assertions();
|
||||
compiler.enable_content_assertions();
|
||||
}
|
||||
compiler.use_loader(Box::new(RemotesLoader(suite.to_owned())));
|
||||
compiler.add_resource(url, group.schema)?;
|
||||
let sch_index = compiler.compile(url, &mut schemas)?;
|
||||
for test in group.tests {
|
||||
println!(" {}", test.description);
|
||||
let result = schemas.validate(&test.data, sch_index);
|
||||
if let Err(e) = &result {
|
||||
for line in format!("{e}").lines() {
|
||||
println!(" {line}");
|
||||
}
|
||||
for line in format!("{e:#}").lines() {
|
||||
println!(" {line}");
|
||||
}
|
||||
}
|
||||
assert_eq!(result.is_ok(), test.valid);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct RemotesLoader(String);
|
||||
impl UrlLoader for RemotesLoader {
|
||||
fn load(&self, url: &str) -> Result<Value, Box<dyn std::error::Error>> {
|
||||
// remotes folder --
|
||||
if let Some(path) = url.strip_prefix("http://localhost:1234/") {
|
||||
let path = Path::new(&self.0).join("remotes").join(path);
|
||||
let file = File::open(path)?;
|
||||
let json: Value = serde_json::from_reader(file)?;
|
||||
return Ok(json);
|
||||
}
|
||||
Err("no internet")?
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user