231 lines
7.0 KiB
Rust
231 lines
7.0 KiB
Rust
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(())
|
|
}
|