use std::fs;
use std::io::{self, BufWriter, Write};
use std::panic::{self, AssertUnwindSafe};
use serde_json::{json, Value};
use std::sync::Arc;
use wellformed::runtime::{NamedPredicate, PredicateRegistry};
use wellformed::{validate, validate_with_registry, Schema};
fn main() {
panic::set_hook(Box::new(|_| {}));
let path = std::env::args()
.nth(1)
.expect("usage: fuzz_driver <cases.ndjson>");
let data = fs::read_to_string(&path).expect("read cases file");
let stdout = io::stdout();
let mut out = BufWriter::new(stdout.lock());
for line in data.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let result = panic::catch_unwind(AssertUnwindSafe(|| run_case(line)));
let value = match result {
Ok(v) => v,
Err(_) => json!({ "panic": true }),
};
writeln!(out, "{value}").expect("write result");
}
}
fn run_case(line: &str) -> Value {
let case: Value = match serde_json::from_str(line) {
Ok(v) => v,
Err(e) => return json!({ "line_error": e.to_string() }),
};
let schema: Schema = match serde_json::from_value(case["schema"].clone()) {
Ok(s) => s,
Err(e) => return json!({ "parse_error": e.to_string() }),
};
if case.get("mode").and_then(Value::as_str) == Some("serde") {
return match serde_json::to_value(&schema) {
Ok(schema) => json!({ "ok": true, "schema": schema }),
Err(e) => json!({ "err": e.to_string() }),
};
}
let mut input = case["input"].clone();
let result = if case.get("registry").and_then(Value::as_str) == Some("custom") {
let mut registry = PredicateRegistry::with_builtins();
registry.register(Arc::new(CustomEvenPredicate));
validate_with_registry(&schema, &mut input, ®istry)
} else {
validate(&schema, &mut input)
};
match result {
Ok(r) => {
let valid = r.is_valid();
json!({
"valid": valid,
"value": input,
"errors": r.errors,
"warnings": r.warnings,
})
}
Err(e) => json!({ "err": format!("{e:?}") }),
}
}
struct CustomEvenPredicate;
impl NamedPredicate for CustomEvenPredicate {
fn name(&self) -> &str {
"custom_is_even"
}
fn evaluate(&self, value: &Value, _args: &Value) -> bool {
match value {
Value::Number(n) => n.as_i64().is_some_and(|x| x % 2 == 0),
Value::String(s) => s.parse::<i64>().is_ok_and(|x| x % 2 == 0),
_ => false,
}
}
}