use assert_json_diff::assert_json_eq;
use cdevents_sdk::CDEvent;
use rstest::*;
use std::{collections::HashMap, fs};
use std::path::PathBuf;
use proptest::prelude::*;
use boon::{Compiler, SchemaIndex, Schemas, UrlLoader};
use glob::glob;
use std::sync::OnceLock;
struct EventsSchemas {
schemas: Schemas,
mapping: HashMap<String, SchemaIndex>,
}
impl EventsSchemas {
fn load() -> Self {
let mut schemas = Schemas::new();
let mut compiler = Compiler::new();
let mut mapping = HashMap::new();
compiler.use_loader(Box::new(HackUrlLoader{}));
for entry in glob("../cdevents-specs/*/schemas/*.json").expect("Failed to read glob pattern") {
let schemapath = entry.unwrap();
let jsonschema: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(&schemapath).unwrap()).unwrap();
let ty = jsonschema["properties"]["context"]["properties"]["type"]["default"].as_str()
.unwrap_or_default()
.to_string();
mapping.entry(ty).or_insert_with(|| {
let sch_index = compiler.compile(&schemapath.to_string_lossy(), &mut schemas);
if let Err(err) = sch_index {
panic!("{err:#}"); }
sch_index.unwrap()
});
}
Self {
schemas, mapping
}
}
fn check_against_schema(&self, json: &serde_json::Value, ty: &str) {
let sch_index = self.mapping.get(ty).unwrap_or_else(|| panic!("to have schema for {ty}"));
let result = self.schemas.validate(json, *sch_index);
if let Err(err) = result {
panic!("{err}");
}
}
}
struct HackUrlLoader;
impl UrlLoader for HackUrlLoader {
fn load(&self, url: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
let re = regex::Regex::new(r"https://cdevents.dev/(?<version>\d+\.\d+)\.\d+/schema/(?<path>.*)")?;
let re_draft = regex::Regex::new(r"https://cdevents.dev/(?<version>\d+\.\d+)\.\d+-draft/schema/(?<path>.*)")?;
if let Some(caps) = re.captures(url) {
let path = format!("../cdevents-specs/spec-v{}/schemas/{}.json", &caps["version"], &caps["path"]);
let jsonschema: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path)?)?;
Ok(jsonschema)
} else if let Some(caps) = re_draft.captures(url) {
let path = format!("../cdevents-specs/main/schemas/{}.json", &caps["path"]);
let jsonschema: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path)?)?;
Ok(jsonschema)
} else if url.starts_with("https://cdevents.dev/schema/links/") {
let path = url.replace("https://cdevents.dev/schema", "../cdevents-specs/spec-v0.4/schemas");
let jsonschema: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path)?)?;
Ok(jsonschema)
} else if url.starts_with("file://") {
let path = url.replace("file://", "");
let jsonschema: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path)?)?;
Ok(jsonschema)
} else {
Err(format!("fail to load {url}").into())
}
}
}
static EVENTS_SCHEMA_CELL: OnceLock<EventsSchemas> = OnceLock::new();
fn events_schemas() -> &'static EventsSchemas {
EVENTS_SCHEMA_CELL.get_or_init(EventsSchemas::load)
}
#[rstest]
fn can_serde_example(
#[files("../cdevents-specs/spec-*/examples/*.json")]
#[files("../cdevents-specs/spec-*/conformance/*.json")]
#[files("../cdevents-specs/spec-*/custom/conformance.json")]
#[files("examples/samples_ok/*.json")]
path: PathBuf) {
let example_txt = fs::read_to_string(path).expect("to read file as string");
let example_json: serde_json::Value =
serde_json::from_str(&example_txt).expect("to parse as json");
let cdevent: CDEvent =
serde_json::from_value(example_json.clone()).expect("to parse as cdevent");
let cdevent_json = serde_json::to_value(cdevent).expect("to convert into json");
assert_json_eq!(example_json, cdevent_json);
}
#[rstest]
fn validate_example_against_schema(#[files("../cdevents-specs/spec-*/examples/*.json")] #[files("../cdevents-specs/spec-*/conformance/*.json")] path: PathBuf) {
let events_schemas = events_schemas();
let example_txt = fs::read_to_string(path).expect("to read file as string");
let example_json: serde_json::Value =
serde_json::from_str(&example_txt).expect("to parse as json");
let ty = example_json["context"]["type"].as_str().expect("valid context.type in json");
events_schemas.check_against_schema(&example_json, ty);
}
proptest! {
#[test]
#[cfg(feature = "testkit")]
fn arbitraries_check_jsonschema(s in any::<CDEvent>()) {
let events_schemas = events_schemas();
let json = serde_json::to_value(&s).unwrap();
events_schemas.check_against_schema(&json, s.ty());
}
}