use std::path::Path;
use std::{env, fs};
use jsonfg::{Feature, FeatureCollection, Geometry};
use serde_json::Value;
fn main() {
let dir = env::args().nth(1).expect("usage: validate <dir>");
let (mut total, mut ok, mut mismatch, mut err) = (0u32, 0u32, 0u32, 0u32);
let mut files: Vec<_> = Vec::new();
collect(Path::new(&dir), &mut files);
files.sort();
for path in files {
total += 1;
let text = fs::read_to_string(&path).unwrap();
let original: Value = match serde_json::from_str(&text) {
Ok(v) => v,
Err(e) => {
err += 1;
println!("ERR parse {}: {e}", path.display());
continue;
}
};
let kind = original.get("type").and_then(Value::as_str).unwrap_or("");
let back = match kind {
"Feature" => {
serde_json::from_value::<Feature>(original.clone()).and_then(serde_json::to_value)
}
"FeatureCollection" => serde_json::from_value::<FeatureCollection>(original.clone())
.and_then(serde_json::to_value),
_ => {
serde_json::from_value::<Geometry>(original.clone()).and_then(serde_json::to_value)
}
};
match back {
Ok(back) if normalize(&back) == normalize(&original) => {
ok += 1;
println!("OK {:<18} {}", kind, path.display());
}
Ok(back) => {
mismatch += 1;
println!("DIFF {:<18} {}", kind, path.display());
for key in dropped_keys(&normalize(&original), &normalize(&back)) {
println!(" changed member: {key}");
}
}
Err(e) => {
err += 1;
println!("ERR decode {}: {e}", path.display());
}
}
}
println!("\n{total} files: {ok} ok, {mismatch} mismatch, {err} error");
}
fn dropped_keys(a: &Value, b: &Value) -> Vec<String> {
match (a, b) {
(Value::Object(a), Value::Object(b)) => a
.iter()
.filter(|(k, v)| b.get(*k) != Some(*v))
.map(|(k, _)| k.clone())
.collect(),
_ => Vec::new(),
}
}
fn normalize(v: &Value) -> Value {
match v {
Value::Number(n) => n
.as_f64()
.and_then(serde_json::Number::from_f64)
.map(Value::Number)
.unwrap_or_else(|| v.clone()),
Value::Array(a) => Value::Array(a.iter().map(normalize).collect()),
Value::Object(o) => {
Value::Object(o.iter().map(|(k, x)| (k.clone(), normalize(x))).collect())
}
other => other.clone(),
}
}
fn collect(dir: &Path, out: &mut Vec<std::path::PathBuf>) {
for entry in fs::read_dir(dir).unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
collect(&path, out);
} else if path.extension().is_some_and(|e| e == "json") {
out.push(path);
}
}
}