use serde_json::Value;
pub fn ok(value: &Value, pretty: bool) -> ! {
let rendered = if pretty {
serde_json::to_string_pretty(value)
} else {
serde_json::to_string(value)
};
match rendered {
Ok(rendered) => {
println!("{rendered}");
std::process::exit(0);
}
Err(e) => fail("internal", &format!("serializing output: {e}"), 1),
}
}
pub fn fail(kind: &str, message: &str, code: i32) -> ! {
let body = serde_json::json!({ "error": { "kind": kind, "message": message } });
eprintln!("{body}");
std::process::exit(code);
}
pub fn fail_engine(e: &topodb::TopoError) -> ! {
match e {
topodb::TopoError::Rejected(_) => fail("rejected", &e.to_string(), 2),
topodb::TopoError::Busy => fail("busy", &e.to_string(), 3),
_ => fail("internal", &e.to_string(), 1),
}
}
pub fn render(json: &Value, text: Option<String>, text_mode: bool, pretty: bool) -> ! {
if text_mode {
if let Some(t) = text {
println!("{t}");
std::process::exit(0);
}
}
ok(json, pretty)
}
pub fn empty_scope_echo(scope: &str, source: &str) {
eprintln!("topodb: 0 matches in scope {scope} (source: {source})");
}
pub fn time_filter_echo(desc: &str, source: &str, after_ms: Option<i64>, before_ms: Option<i64>) {
let bound = |ms: Option<i64>| ms.map_or("..".to_string(), iso_utc);
eprintln!(
"topodb: time filter: {desc} = [{}, {}) (source: {source})",
bound(after_ms),
bound(before_ms),
);
}
fn iso_utc(ms: i64) -> String {
const DAY_MS: i64 = 86_400_000;
let days = ms.div_euclid(DAY_MS);
let rem = ms.rem_euclid(DAY_MS);
let z = days + 719_468;
let era = z.div_euclid(146_097);
let doe = z - era * 146_097;
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = if m <= 2 { y + 1 } else { y };
if rem == 0 {
format!("{y:04}-{m:02}-{d:02}")
} else {
let (h, min, s) = (rem / 3_600_000, (rem / 60_000) % 60, (rem / 1000) % 60);
format!("{y:04}-{m:02}-{d:02}T{h:02}:{min:02}:{s:02}Z")
}
}
#[cfg(test)]
mod tests {
use super::iso_utc;
#[test]
fn iso_utc_matches_the_parser_anchors() {
assert_eq!(iso_utc(1_785_542_400_000), "2026-08-01");
assert_eq!(iso_utc(1_767_225_600_000), "2026-01-01");
assert_eq!(
iso_utc(1_785_542_400_000 + 55_800_000),
"2026-08-01T15:30:00Z"
);
assert_eq!(iso_utc(21_243 * 86_400_000), "2028-02-29");
}
}