use crate::diagnose::Diagnosis;
use crate::evidence::Evidence;
use std::fmt::Write;
pub fn render_short(d: &Diagnosis) -> String {
let mut s = String::new();
let _ = writeln!(s, "CASE: {}", d.case);
let _ = writeln!(s, "RULE: {}", d.rule);
let _ = writeln!(
s,
"SEVERITY: {} ({})",
d.severity.as_str(),
d.severity_source.label()
);
let _ = writeln!(s, "LIKELY CAUSE: {}", d.likely_cause);
if !d.evidence.is_empty() {
s.push_str("EVIDENCE:\n");
for e in &d.evidence {
let _ = writeln!(s, "- {}", render_evidence(e));
}
}
let _ = writeln!(s, "REPRODUCTION:\n{}", d.reproduction);
s
}
pub fn render_report(d: &Diagnosis) -> String {
let mut s = String::new();
let _ = writeln!(s, "CASE: {}", d.case);
let _ = writeln!(s, "RULE: {}", d.rule);
let _ = writeln!(
s,
"SEVERITY: {} ({}: {})",
d.severity.as_str(),
d.severity_source.label(),
d.severity_source.rationale()
);
let _ = writeln!(s, "LIKELY CAUSE: {}", d.likely_cause);
s.push('\n');
s.push_str("EVIDENCE:\n");
if d.evidence.is_empty() {
s.push_str("- (none collected)\n");
} else {
for e in &d.evidence {
let _ = writeln!(s, "- {}", render_evidence(e));
}
}
s.push('\n');
s.push_str("HYPOTHESES (consistent with evidence; not asserted as fact):\n");
if d.hypotheses.is_empty() {
s.push_str("- (none)\n");
} else {
for h in &d.hypotheses {
let _ = writeln!(s, "- {h}");
}
}
s.push('\n');
s.push_str("UNKNOWNS (do not invent answers):\n");
if d.unknowns.is_empty() {
s.push_str("- (none)\n");
} else {
for u in &d.unknowns {
let _ = writeln!(s, "- {u}");
}
}
s.push('\n');
let _ = writeln!(s, "REPRODUCTION:\n{}\n", d.reproduction);
s.push_str("NEXT STEPS:\n");
for (i, step) in d.next_steps.iter().enumerate() {
let _ = writeln!(s, "{}. {}", i + 1, step);
}
s.push('\n');
let _ = write!(s, "ESCALATION NOTE:\n{}\n", d.escalation_note);
s
}
pub fn render_evidence(e: &Evidence) -> String {
match e {
Evidence::HttpStatus(code) => format!("HTTP status: {code}"),
Evidence::HeaderPresent { name, value } => match value {
Some(v) => format!("Request header present: {name} = {v}"),
None => format!("Request header present: {name}"),
},
Evidence::HeaderMissing { name } => {
format!("Request header missing: {name}")
}
Evidence::BodyMutatedBeforeVerification => {
"Request body was modified by middleware before verification.".into()
}
Evidence::SignatureMismatch => "HMAC signature verification failed.".into(),
Evidence::ClockDriftSecs {
observed,
tolerance_secs,
} => format!("Clock drift {observed}s exceeds tolerance {tolerance_secs}s."),
Evidence::RetryAfterSecs(secs) => format!("Retry-After header: {secs}s"),
Evidence::RateLimitObserved {
observed_rps,
limit_rps,
} => format!("Observed rate {observed_rps} rps exceeds account limit {limit_rps} rps."),
Evidence::DnsResolutionFailed { host, message } => {
format!("DNS resolution failed for {host}: {message}")
}
Evidence::TlsHandshakeFailed { peer, reason } => {
format!("TLS handshake to {peer} failed: {reason}")
}
Evidence::ConnectionTimeout {
elapsed_ms,
timeout_ms,
} => format!("Client timeout: aborted after {elapsed_ms}ms (timeout {timeout_ms}ms)."),
Evidence::JsonValidationError { field, message } => match field {
Some(f) => format!("JSON validation error on field `{f}`: {message}"),
None => format!("JSON validation error: {message}"),
},
}
}