use std::path::Path;
use jsonschema::{Draft, JSONSchema};
use serde_json::{json, Value};
use crate::data;
use crate::output::{emit_ok, exit, CliError, Ctx};
use crate::schemas;
const ENTITIES: &[&str] = &["people", "products", "customers", "chats", "repos"];
#[derive(serde::Serialize)]
struct EntityReport {
entity: String,
ok: bool,
errors: Vec<SchemaError>,
}
#[derive(serde::Serialize)]
struct SchemaError {
path: String,
message: String,
}
pub fn run(dir: &Path, ctx: Ctx) -> Result<(), CliError> {
let mut reports: Vec<EntityReport> = Vec::new();
let mut all_ok = true;
for entity in ENTITIES {
let raw = schemas::for_entity(entity).expect("embedded schema");
let schema: Value = serde_json::from_str(raw).expect("schema is valid JSON");
let compiled = JSONSchema::options()
.with_draft(Draft::Draft7)
.compile(&schema)
.map_err(|e| {
CliError::data(
format!("internal: failed to compile schema for {entity}: {e}"),
None,
)
})?;
let instance = data::load(dir, entity)?;
let mut errors: Vec<SchemaError> = Vec::new();
if let Err(iter) = compiled.validate(&instance) {
for err in iter {
errors.push(SchemaError {
path: err.instance_path.to_string(),
message: err.to_string(),
});
}
}
let ok = errors.is_empty();
if !ok {
all_ok = false;
}
reports.push(EntityReport {
entity: (*entity).to_string(),
ok,
errors,
});
}
let payload = json!({
"ok": all_ok,
"data_dir": dir.display().to_string(),
"reports": reports,
});
if !all_ok {
emit_ok(ctx, payload);
return Err(CliError {
code: exit::VALIDATION,
message: "schema validation failed".into(),
hint: Some("see the per-entity report above; fix the listed paths".into()),
retryable: false,
});
}
if ctx.json {
emit_ok(ctx, payload);
} else {
emit_ok(
ctx,
json!({ "message": format!("ok — {} entities valid", ENTITIES.len()) }),
);
}
Ok(())
}