jspg additional properties bug squashed

This commit is contained in:
2025-09-30 19:56:34 -04:00
parent cc04f38c14
commit d6b34c99bb
26 changed files with 6340 additions and 6328 deletions

View File

@ -5,83 +5,83 @@ 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 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)?;
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(())
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 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);
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(())
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 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)?;
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(())
Ok(())
}

View File

@ -5,37 +5,37 @@ 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());
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}");
}
assert_eq!(result.is_ok(), test["valid"].as_bool().unwrap());
Ok(())
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")?
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")?
}
}

View File

@ -7,16 +7,16 @@ 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 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());
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(())
Ok(())
}
/**
@ -31,200 +31,200 @@ 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 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());
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(())
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});
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)?)
}
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());
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(())
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")?)?;
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)?)
}
}
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());
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(())
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");
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(())
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")?;
}
}
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(())
}
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");
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(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)
}
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());
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(())
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");
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)
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());
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(())
Ok(())
}

View File

@ -3,42 +3,42 @@ 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(())
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())
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("tests/examples/schema.json")
}
#[test]
#[cfg(windows)]
fn test_relative_backslash() -> Result<(), CompileError> {
test("tests\\examples\\schema.json")
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())
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("tests/examples/sample schema.json")
}
#[test]
#[cfg(windows)]
fn test_relative_backslash_space() -> Result<(), CompileError> {
test("tests\\examples\\sample schema.json")
test("tests\\examples\\sample schema.json")
}

View File

@ -6,62 +6,62 @@ use serde_json::Value;
#[derive(Debug, Deserialize)]
struct Test {
description: String,
remotes: Option<HashMap<String, Value>>,
schema: Value,
errors: Option<Vec<String>>,
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");
}
}
}
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(())
}
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(())
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")?
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")?
}
}

View File

@ -6,117 +6,117 @@ 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(())
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(())
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");
}
}
}
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(())
Ok(())
}
#[derive(Debug, Serialize, Deserialize)]
struct Group {
description: String,
schema: Value,
tests: Vec<Test>,
description: String,
schema: Value,
tests: Vec<Test>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Test {
description: String,
data: Value,
output: Output,
description: String,
data: Value,
output: Output,
}
#[derive(Debug, Serialize, Deserialize)]
struct Output {
basic: Option<Value>,
detailed: Option<Value>,
basic: Option<Value>,
detailed: Option<Value>,
}

View File

@ -5,116 +5,116 @@ 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",
"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>,
description: String,
schema: Value,
tests: Vec<Test>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Test {
description: String,
data: Value,
valid: bool,
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(())
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(())
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(());
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)?;
}
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(())
}
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)?;
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);
}
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();
}
Ok(())
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")?
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")?
}
}