llm_assisted_api_debugging_lab/
report.rs1use crate::diagnose::Diagnosis;
21use crate::evidence::Evidence;
22use std::fmt::Write;
23
24pub fn render_short(d: &Diagnosis) -> String {
33 let mut s = String::new();
34 let _ = writeln!(s, "CASE: {}", d.case);
35 let _ = writeln!(s, "RULE: {}", d.rule);
36 let _ = writeln!(
37 s,
38 "SEVERITY: {} ({})",
39 d.severity.as_str(),
40 d.severity_source.label()
41 );
42 let _ = writeln!(s, "LIKELY CAUSE: {}", d.likely_cause);
43 if !d.evidence.is_empty() {
44 s.push_str("EVIDENCE:\n");
45 for e in &d.evidence {
46 let _ = writeln!(s, "- {}", render_evidence(e));
47 }
48 }
49 let _ = writeln!(s, "REPRODUCTION:\n{}", d.reproduction);
50 s
51}
52
53pub fn render_report(d: &Diagnosis) -> String {
65 let mut s = String::new();
66
67 let _ = writeln!(s, "CASE: {}", d.case);
68 let _ = writeln!(s, "RULE: {}", d.rule);
69 let _ = writeln!(
70 s,
71 "SEVERITY: {} ({}: {})",
72 d.severity.as_str(),
73 d.severity_source.label(),
74 d.severity_source.rationale()
75 );
76 let _ = writeln!(s, "LIKELY CAUSE: {}", d.likely_cause);
77 s.push('\n');
78
79 s.push_str("EVIDENCE:\n");
80 if d.evidence.is_empty() {
81 s.push_str("- (none collected)\n");
82 } else {
83 for e in &d.evidence {
84 let _ = writeln!(s, "- {}", render_evidence(e));
85 }
86 }
87 s.push('\n');
88
89 s.push_str("HYPOTHESES (consistent with evidence; not asserted as fact):\n");
90 if d.hypotheses.is_empty() {
91 s.push_str("- (none)\n");
92 } else {
93 for h in &d.hypotheses {
94 let _ = writeln!(s, "- {h}");
95 }
96 }
97 s.push('\n');
98
99 s.push_str("UNKNOWNS (do not invent answers):\n");
100 if d.unknowns.is_empty() {
101 s.push_str("- (none)\n");
102 } else {
103 for u in &d.unknowns {
104 let _ = writeln!(s, "- {u}");
105 }
106 }
107 s.push('\n');
108
109 let _ = writeln!(s, "REPRODUCTION:\n{}\n", d.reproduction);
110
111 s.push_str("NEXT STEPS:\n");
112 for (i, step) in d.next_steps.iter().enumerate() {
113 let _ = writeln!(s, "{}. {}", i + 1, step);
114 }
115 s.push('\n');
116
117 let _ = write!(s, "ESCALATION NOTE:\n{}\n", d.escalation_note);
118
119 s
120}
121
122pub fn render_evidence(e: &Evidence) -> String {
131 match e {
132 Evidence::HttpStatus(code) => format!("HTTP status: {code}"),
133 Evidence::HeaderPresent { name, value } => match value {
134 Some(v) => format!("Request header present: {name} = {v}"),
135 None => format!("Request header present: {name}"),
136 },
137 Evidence::HeaderMissing { name } => {
138 format!("Request header missing: {name}")
139 }
140 Evidence::BodyMutatedBeforeVerification => {
141 "Request body was modified by middleware before verification.".into()
142 }
143 Evidence::SignatureMismatch => "HMAC signature verification failed.".into(),
144 Evidence::ClockDriftSecs {
145 observed,
146 tolerance_secs,
147 } => format!("Clock drift {observed}s exceeds tolerance {tolerance_secs}s."),
148 Evidence::RetryAfterSecs(secs) => format!("Retry-After header: {secs}s"),
149 Evidence::RateLimitObserved {
150 observed_rps,
151 limit_rps,
152 } => format!("Observed rate {observed_rps} rps exceeds account limit {limit_rps} rps."),
153 Evidence::DnsResolutionFailed { host, message } => {
154 format!("DNS resolution failed for {host}: {message}")
155 }
156 Evidence::TlsHandshakeFailed { peer, reason } => {
157 format!("TLS handshake to {peer} failed: {reason}")
158 }
159 Evidence::ConnectionTimeout {
160 elapsed_ms,
161 timeout_ms,
162 } => format!("Client timeout: aborted after {elapsed_ms}ms (timeout {timeout_ms}ms)."),
163 Evidence::JsonValidationError { field, message } => match field {
164 Some(f) => format!("JSON validation error on field `{f}`: {message}"),
165 None => format!("JSON validation error: {message}"),
166 },
167 }
168}