#![cfg(feature = "json")]
use std::path::PathBuf;
use quarto_error_reporting::{JsonDiagnostic, JsonPass1Failure};
use serde_json::Value;
fn canonicalize_keys(value: Value) -> Value {
match value {
Value::Object(map) => {
let mut entries: Vec<(String, Value)> = map.into_iter().collect();
entries.sort_by(|a, b| a.0.cmp(&b.0));
let mut out = serde_json::Map::new();
for (k, v) in entries {
out.insert(k, canonicalize_keys(v));
}
Value::Object(out)
}
Value::Array(arr) => Value::Array(arr.into_iter().map(canonicalize_keys).collect()),
other => other,
}
}
fn render_schema<T: schemars::JsonSchema>() -> String {
let schema = schemars::schema_for!(T);
let value = serde_json::to_value(&schema).expect("schema must serialize to Value");
let canonical = canonicalize_keys(value);
let mut s = serde_json::to_string_pretty(&canonical).expect("schema must serialize");
s.push('\n');
s
}
fn schemas_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("schemas")
}
fn check_or_regenerate(file_name: &str, generated: &str) {
let path = schemas_dir().join(file_name);
let regen = std::env::var("QUARTO_REGEN_SCHEMAS").is_ok_and(|v| v == "1");
let existing = std::fs::read_to_string(&path).ok();
if existing.as_deref() == Some(generated) {
return;
}
if regen {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("create schemas/ dir");
}
std::fs::write(&path, generated)
.unwrap_or_else(|e| panic!("failed to write schema to {}: {}", path.display(), e));
eprintln!("regenerated {}", path.display());
return;
}
let existing_summary = match &existing {
None => "(no file on disk)".to_string(),
Some(s) => format!("({} bytes)", s.len()),
};
panic!(
"JSON Schema drift for {}.\n\
The checked-in file {} {} does not match the schema generated\n\
from the current Rust types in src/json.rs. To regenerate, run:\n\
\n\
\tQUARTO_REGEN_SCHEMAS=1 cargo nextest run -p quarto-error-reporting --test schema_drift\n\
\n\
Then review the diff before committing.",
file_name,
path.display(),
existing_summary,
);
}
#[test]
fn json_diagnostic_schema_matches_committed() {
check_or_regenerate("json-diagnostic.json", &render_schema::<JsonDiagnostic>());
}
#[test]
fn json_pass1_failure_schema_matches_committed() {
check_or_regenerate(
"json-pass1-failure.json",
&render_schema::<JsonPass1Failure>(),
);
}