use std::fmt::Display;
use std::sync::atomic::{AtomicU64, Ordering};
use axum::{http::StatusCode, response::Json};
use serde_json::json;
static CORRELATION_COUNTER: AtomicU64 = AtomicU64::new(0);
pub fn correlation_id() -> String {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let seq = CORRELATION_COUNTER.fetch_add(1, Ordering::Relaxed);
let mixed = nanos ^ seq.rotate_left(40);
format!("{mixed:016x}")
}
pub fn internal_error(context: &str, detail: impl Display) -> (StatusCode, Json<serde_json::Value>) {
let cid = correlation_id();
tracing::error!(
correlation_id = %cid,
context = context,
detail = %detail,
"internal error (detail logged server-side only; client received a generic body)"
);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": "internal_error",
"correlation_id": cid,
"success": false,
})),
)
}
pub fn internal_error_json(context: &str, detail: impl Display) -> Json<serde_json::Value> {
let cid = correlation_id();
tracing::error!(
correlation_id = %cid,
context = context,
detail = %detail,
"internal error (detail logged server-side only; client received a generic body)"
);
Json(json!({
"error": "internal_error",
"correlation_id": cid,
"success": false,
}))
}
pub fn upstream_unavailable(
context: &str,
detail: impl Display,
) -> (StatusCode, Json<serde_json::Value>) {
let cid = correlation_id();
tracing::warn!(
correlation_id = %cid,
context = context,
detail = %detail,
"upstream unavailable (detail logged server-side only; client received a generic body)"
);
(
StatusCode::SERVICE_UNAVAILABLE,
Json(json!({
"error": "upstream_unavailable",
"correlation_id": cid,
})),
)
}
#[cfg(test)]
mod tests {
use super::*;
const LEAKY_DETAIL: &str =
"task 42 panicked at 'C:\\Users\\ruv\\secret\\models\\foo.rvf': No such file or directory (os error 2)";
fn all_strings(v: &serde_json::Value, out: &mut Vec<String>) {
match v {
serde_json::Value::String(s) => out.push(s.clone()),
serde_json::Value::Array(a) => a.iter().for_each(|x| all_strings(x, out)),
serde_json::Value::Object(o) => o.values().for_each(|x| all_strings(x, out)),
_ => {}
}
}
fn body_strings(body: &Json<serde_json::Value>) -> Vec<String> {
let mut out = Vec::new();
all_strings(&body.0, &mut out);
out
}
#[test]
fn internal_error_body_does_not_leak_detail() {
let (status, body) = internal_error("unit-test", LEAKY_DETAIL);
assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
for s in body_strings(&body) {
assert!(
!s.contains("panicked"),
"response body leaked the panic message: {s:?}"
);
assert!(
!s.contains("secret"),
"response body leaked a filesystem path: {s:?}"
);
assert!(
!s.contains("os error"),
"response body leaked an OS error string: {s:?}"
);
assert!(
!s.contains(".rvf"),
"response body leaked a file name/path: {s:?}"
);
}
}
#[test]
fn internal_error_body_is_generic_with_correlation_id() {
let (_status, body) = internal_error("unit-test", LEAKY_DETAIL);
assert_eq!(body.0["error"], "internal_error");
assert_eq!(body.0["success"], false);
let cid = body.0["correlation_id"]
.as_str()
.expect("correlation_id must be a string");
assert_eq!(cid.len(), 16, "correlation id should be 16 hex chars");
assert!(
cid.chars().all(|c| c.is_ascii_hexdigit()),
"correlation id should be hex: {cid:?}"
);
}
#[test]
fn internal_error_json_does_not_leak_detail() {
let body = internal_error_json("unit-test", LEAKY_DETAIL);
assert_eq!(body.0["error"], "internal_error");
assert_eq!(body.0["success"], false);
for s in body_strings(&body) {
assert!(!s.contains("panicked"), "leaked panic message: {s:?}");
assert!(!s.contains("secret"), "leaked filesystem path: {s:?}");
assert!(!s.contains("os error"), "leaked OS error: {s:?}");
}
}
#[test]
fn upstream_unavailable_does_not_leak_detail() {
let (status, body) = upstream_unavailable(
"edge-registry",
"https://internal-host.local:9000/app-registry.json: connection refused",
);
assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
for s in body_strings(&body) {
assert!(
!s.contains("internal-host"),
"leaked internal upstream host: {s:?}"
);
assert!(
!s.contains("connection refused"),
"leaked upstream connection detail: {s:?}"
);
}
assert_eq!(body.0["error"], "upstream_unavailable");
assert!(body.0["correlation_id"].is_string());
}
#[test]
fn correlation_ids_are_unique() {
let a = correlation_id();
let b = correlation_id();
assert_ne!(a, b, "successive correlation ids must differ: {a} == {b}");
}
}