use json_schema_ast::SchemaDocument;
use json_schema_fuzz::{GenerationConfig, ValueGenerator};
use jsoncompat::{Role, check_compat};
use rand::{SeedableRng, rngs::StdRng};
use serde::Deserialize;
use serde_json::Value;
use std::fs;
use std::path::{Path, PathBuf};
datatest_stable::harness! {
{ test = fixture, root = "tests/fixtures/backcompat", pattern = r".*[/\\]expect\.json$" },
}
#[derive(Deserialize)]
struct Expectation {
serializer: bool,
deserializer: bool,
#[serde(default)]
allowed_failure: bool,
}
#[derive(Deserialize, Default)]
struct SampleSets {
#[serde(default)]
old_only: Vec<Value>,
#[serde(default)]
new_only: Vec<Value>,
#[serde(default)]
both: Vec<Value>,
}
fn fixture(expect_file: &Path) -> Result<(), Box<dyn std::error::Error>> {
let dir: PathBuf = expect_file.parent().unwrap().into();
let old_raw: Value = serde_json::from_slice(&fs::read(dir.join("old.json"))?)?;
let new_raw: Value = serde_json::from_slice(&fs::read(dir.join("new.json"))?)?;
let expect: Expectation = serde_json::from_slice(&fs::read(expect_file)?)?;
let old_schema = SchemaDocument::from_json(&old_raw)?;
let new_schema = SchemaDocument::from_json(&new_raw)?;
let ser = check_compat(&old_schema, &new_schema, Role::Serializer)?;
let de = check_compat(&old_schema, &new_schema, Role::Deserializer)?;
if expect.allowed_failure {
if ser == expect.serializer && de == expect.deserializer {
panic!("Previously-failing fixture now passes; remove allowed_failure from {dir:?}");
}
} else {
assert_eq!(ser, expect.serializer, "serializer mismatch in {dir:?}");
assert_eq!(de, expect.deserializer, "deserializer mismatch in {dir:?}");
}
let ex_path = dir.join("examples.json");
if ex_path.exists() {
let samples: SampleSets = serde_json::from_slice(&fs::read(&ex_path)?)?;
for v in &samples.old_only {
assert!(
old_schema.is_valid(v)?,
"old_only invalid in {dir:?}: {v:?}"
);
assert!(
!new_schema.is_valid(v)?,
"old_only accepted by NEW in {dir:?}: {v:?}"
);
}
for v in &samples.new_only {
assert!(
new_schema.is_valid(v)?,
"new_only invalid in {dir:?}: {v:?}"
);
assert!(
!old_schema.is_valid(v)?,
"new_only accepted by OLD in {dir:?}: {v:?}"
);
}
for v in &samples.both {
assert!(
old_schema.is_valid(v)? && new_schema.is_valid(v)?,
"both sample invalid in {dir:?}: {v:?}"
);
}
}
let mut rng = StdRng::seed_from_u64(0xDEADBEEF + dir.to_string_lossy().len() as u64);
let config = GenerationConfig::new(4);
for _ in 0..100 {
let v_new = ValueGenerator::generate(&new_schema, config, &mut rng)?;
if expect.serializer {
assert!(
old_schema.is_valid(&v_new)?,
"serializer=true but OLD rejects generated value {v_new:?} in {dir:?}"
);
}
let v_old = ValueGenerator::generate(&old_schema, config, &mut rng)?;
if expect.deserializer {
assert!(
new_schema.is_valid(&v_old)?,
"deserializer=true but NEW rejects generated value {v_old:?} in {dir:?}"
);
}
}
Ok(())
}