use std::process::ExitCode;
use verit::{codegen, dump_json, Budget, Message, Resolver};
const USAGE: &str = "\
verit — Exavian Veritate CLI
USAGE:
verit <COMMAND> <FILE>
COMMANDS:
dump <msg> Render the message as JSON from its bytes alone (needs inline schema).
id <msg> Print the 128-bit schema id carried in the envelope.
verify <msg> Walk the whole message under a traversal budget; report ok or the error.
gen <msg> Emit a typed Rust reader/writer for the message's (inline) schema.
build <schema.verit> Compile a .verit IDL file: print its schema id, or with
--lang rust|python|ts|go|cpp emit typed bindings to stdout.
Every schema-aware command (dump/id/verify/gen) needs a message encoded with the
inline schema. `build` operates on a .verit schema file instead. Bindings emitted
for any language embed the same canonical schema + 128-bit id, so their wire bytes
are byte-identical across all five.
";
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
match run(&args) {
Ok(out) => {
print!("{out}");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("verit: {e}");
ExitCode::FAILURE
}
}
}
fn run(args: &[String]) -> Result<String, String> {
let cmd = args.first().map(String::as_str).unwrap_or("");
if matches!(cmd, "" | "-h" | "--help" | "help") {
return Ok(USAGE.to_string());
}
if matches!(cmd, "-V" | "--version" | "version") {
return Ok(format!("verit {}\n", verit::VERSION));
}
if cmd == "build" {
return build_cmd(&args[1..]);
}
let file = args
.get(1)
.ok_or_else(|| format!("`{cmd}` needs a message file\n\n{USAGE}"))?;
let bytes = std::fs::read(file).map_err(|e| format!("reading {file}: {e}"))?;
match cmd {
"dump" => {
let json = dump_json(&bytes).map_err(|e| e.to_string())?;
Ok(format!("{json}\n"))
}
"id" => {
let msg = Message::parse(&bytes).map_err(|e| e.to_string())?;
Ok(format!("{:032x}\n", msg.schema_id()))
}
"verify" => {
let msg = Message::parse(&bytes).map_err(|e| e.to_string())?;
let schema = msg
.writer_schema()
.map_err(|e| e.to_string())?
.ok_or("message has no inline schema to verify against")?;
let resolver = Resolver::identity(&schema).map_err(|e| e.to_string())?;
let budget = Budget::new(msg.suggested_budget());
msg.verify(&resolver, &budget).map_err(|e| e.to_string())?;
Ok(format!(
"ok: {} bytes, traversal within budget ({} of {} bytes left)\n",
bytes.len(),
budget.remaining(),
msg.suggested_budget()
))
}
"gen" => {
let msg = Message::parse(&bytes).map_err(|e| e.to_string())?;
let schema = msg
.writer_schema()
.map_err(|e| e.to_string())?
.ok_or("message has no inline schema to generate from")?;
codegen::generate_rust(&schema).map_err(|e| e.to_string())
}
other => Err(format!("unknown command `{other}`\n\n{USAGE}")),
}
}
fn build_cmd(rest: &[String]) -> Result<String, String> {
let mut path: Option<&str> = None;
let mut lang: Option<String> = None;
let mut it = rest.iter();
while let Some(a) = it.next() {
match a.as_str() {
"--lang" => {
lang = Some(
it.next()
.ok_or("--lang needs a value (rust|python)")?
.clone(),
);
}
s if s.starts_with("--lang=") => lang = Some(s["--lang=".len()..].to_string()),
s if s.starts_with('-') => return Err(format!("unknown flag `{s}`\n\n{USAGE}")),
s if path.is_none() => path = Some(s),
_ => return Err("build takes exactly one .verit file".into()),
}
}
let path = path.ok_or_else(|| format!("build needs a .verit file\n\n{USAGE}"))?;
let src = std::fs::read_to_string(path).map_err(|e| format!("reading {path}: {e}"))?;
let schema = verit::idl::parse(&src).map_err(|e| e.to_string())?;
match lang.as_deref() {
None => Ok(format!(
"schema id: {:032x}\nroot: {}\ntypes: {}\n",
schema.id(),
schema.type_name(schema.root_index()),
schema.type_count()
)),
Some("rust") => verit::codegen::generate_rust(&schema).map_err(|e| e.to_string()),
Some("python") => verit::codegen::generate_python(&schema).map_err(|e| e.to_string()),
Some("ts") | Some("typescript") => {
verit::codegen::generate_ts(&schema).map_err(|e| e.to_string())
}
Some("go") => verit::codegen::generate_go(&schema).map_err(|e| e.to_string()),
Some("cpp") | Some("c++") => {
verit::codegen::generate_cpp(&schema).map_err(|e| e.to_string())
}
Some(other) => Err(format!(
"unknown --lang `{other}` (expected rust|python|ts|go|cpp)"
)),
}
}