#![forbid(unsafe_code)]
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use aegis_wire_formats::{
AttestListReport, Attestation, CliError, CompatReport, CompatSubmitReport, DoctorReport,
FailureMicroreport, ListReport, Manifest, RecommendReport, UpdateReport, VerifyReport, Version,
};
use schemars::schema_for;
struct Target {
name: &'static str,
relative_path: &'static str,
render: fn() -> Result<String, String>,
}
fn targets() -> Vec<Target> {
vec![
Target {
name: "Manifest",
relative_path: "docs/reference/schemas/aegis-boot-manifest.schema.json",
render: render_manifest_schema,
},
Target {
name: "Attestation",
relative_path: "docs/reference/schemas/aegis-attestation.schema.json",
render: render_attestation_schema,
},
Target {
name: "Version",
relative_path: "docs/reference/schemas/aegis-boot-version.schema.json",
render: render_version_schema,
},
Target {
name: "ListReport",
relative_path: "docs/reference/schemas/aegis-boot-list.schema.json",
render: render_list_schema,
},
Target {
name: "AttestListReport",
relative_path: "docs/reference/schemas/aegis-boot-attest-list.schema.json",
render: render_attest_list_schema,
},
Target {
name: "VerifyReport",
relative_path: "docs/reference/schemas/aegis-boot-verify.schema.json",
render: render_verify_schema,
},
Target {
name: "UpdateReport",
relative_path: "docs/reference/schemas/aegis-boot-update.schema.json",
render: render_update_schema,
},
Target {
name: "RecommendReport",
relative_path: "docs/reference/schemas/aegis-boot-recommend.schema.json",
render: render_recommend_schema,
},
Target {
name: "CompatReport",
relative_path: "docs/reference/schemas/aegis-boot-compat.schema.json",
render: render_compat_schema,
},
Target {
name: "CompatSubmitReport",
relative_path: "docs/reference/schemas/aegis-boot-compat-submit.schema.json",
render: render_compat_submit_schema,
},
Target {
name: "DoctorReport",
relative_path: "docs/reference/schemas/aegis-boot-doctor.schema.json",
render: render_doctor_schema,
},
Target {
name: "CliError",
relative_path: "docs/reference/schemas/aegis-boot-cli-error.schema.json",
render: render_cli_error_schema,
},
Target {
name: "FailureMicroreport",
relative_path: "docs/reference/schemas/aegis-boot-failure-microreport.schema.json",
render: render_failure_microreport_schema,
},
]
}
fn render_manifest_schema() -> Result<String, String> {
render_pretty(&schema_for!(Manifest))
}
fn render_attestation_schema() -> Result<String, String> {
render_pretty(&schema_for!(Attestation))
}
fn render_version_schema() -> Result<String, String> {
render_pretty(&schema_for!(Version))
}
fn render_list_schema() -> Result<String, String> {
render_pretty(&schema_for!(ListReport))
}
fn render_attest_list_schema() -> Result<String, String> {
render_pretty(&schema_for!(AttestListReport))
}
fn render_verify_schema() -> Result<String, String> {
render_pretty(&schema_for!(VerifyReport))
}
fn render_update_schema() -> Result<String, String> {
render_pretty(&schema_for!(UpdateReport))
}
fn render_recommend_schema() -> Result<String, String> {
render_pretty(&schema_for!(RecommendReport))
}
fn render_compat_schema() -> Result<String, String> {
render_pretty(&schema_for!(CompatReport))
}
fn render_compat_submit_schema() -> Result<String, String> {
render_pretty(&schema_for!(CompatSubmitReport))
}
fn render_doctor_schema() -> Result<String, String> {
render_pretty(&schema_for!(DoctorReport))
}
fn render_cli_error_schema() -> Result<String, String> {
render_pretty(&schema_for!(CliError))
}
fn render_failure_microreport_schema() -> Result<String, String> {
render_pretty(&schema_for!(FailureMicroreport))
}
fn render_pretty<T: serde::Serialize>(schema: &T) -> Result<String, String> {
let mut body = serde_json::to_string_pretty(schema)
.map_err(|e| format!("schema-docgen: cannot serialize schema: {e}"))?;
body.push('\n');
Ok(body)
}
fn find_repo_root(start: &Path) -> Result<PathBuf, String> {
let mut cur = start;
loop {
let candidate = cur.join("Cargo.toml");
if candidate.is_file() {
if let Ok(body) = fs::read_to_string(&candidate) {
if body.contains("[workspace]") {
return Ok(cur.to_path_buf());
}
}
}
match cur.parent() {
Some(parent) => cur = parent,
None => {
return Err(format!(
"schema-docgen: could not find workspace Cargo.toml walking up from {}",
start.display()
));
}
}
}
}
enum Mode {
Write,
Check,
}
fn parse_mode(args: &[String]) -> Result<Mode, String> {
match args
.iter()
.map(String::as_str)
.collect::<Vec<_>>()
.as_slice()
{
[] | ["--check"] => Ok(Mode::Check),
["--write"] => Ok(Mode::Write),
_ => Err(format!(
"usage: aegis-wire-formats-schema-docgen [--check|--write] (got: {args:?})"
)),
}
}
fn main() -> ExitCode {
let args: Vec<String> = env::args().skip(1).collect();
let mode = match parse_mode(&args) {
Ok(m) => m,
Err(msg) => {
eprintln!("{msg}");
return ExitCode::from(2);
}
};
let cwd = match env::current_dir() {
Ok(p) => p,
Err(e) => {
eprintln!("schema-docgen: cannot read CWD: {e}");
return ExitCode::from(2);
}
};
let repo_root = match find_repo_root(&cwd) {
Ok(p) => p,
Err(msg) => {
eprintln!("{msg}");
return ExitCode::from(2);
}
};
let mut drift_targets: Vec<&'static str> = Vec::new();
let mut wrote = 0usize;
for target in targets() {
let rendered = match (target.render)() {
Ok(s) => s,
Err(msg) => {
eprintln!("{msg}");
return ExitCode::from(2);
}
};
let out_path = repo_root.join(target.relative_path);
match mode {
Mode::Write => {
if let Some(parent) = out_path.parent() {
if let Err(e) = fs::create_dir_all(parent) {
eprintln!("schema-docgen: cannot create {}: {e}", parent.display());
return ExitCode::from(2);
}
}
if let Err(e) = fs::write(&out_path, &rendered) {
eprintln!("schema-docgen: cannot write {}: {e}", out_path.display());
return ExitCode::from(2);
}
println!(
"schema-docgen: wrote {} ({} schema)",
out_path.display(),
target.name
);
wrote += 1;
}
Mode::Check => {
let committed = fs::read_to_string(&out_path).unwrap_or_default();
if committed == rendered {
println!(
"schema-docgen: OK — {} matches `schema_for!({})` output",
out_path.display(),
target.name
);
} else {
eprintln!(
"schema-docgen: DRIFT — regenerated {} schema differs from committed copy at {}",
target.name,
out_path.display()
);
drift_targets.push(target.name);
}
}
}
}
match mode {
Mode::Write => {
println!("schema-docgen: wrote {wrote} schema file(s)");
ExitCode::SUCCESS
}
Mode::Check => {
if drift_targets.is_empty() {
ExitCode::SUCCESS
} else {
eprintln!();
eprintln!(
"schema-docgen: FAIL — {} schema(s) diverge: {}",
drift_targets.len(),
drift_targets.join(", ")
);
eprintln!(
"Fix: run `cargo run -p aegis-wire-formats --bin aegis-wire-formats-schema-docgen --features schema -- --write` locally and commit the result."
);
ExitCode::from(1)
}
}
}
}