use std::any::Any;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct PanicContext {
pub file: &'static str,
pub line: u32,
pub function: &'static str,
pub message: String,
pub timestamp: SystemTime,
}
impl PanicContext {
pub fn new(file: &'static str, line: u32, function: &'static str, panic_info: &dyn Any) -> Self {
let timestamp = std::panic::catch_unwind(SystemTime::now).unwrap_or(UNIX_EPOCH);
Self {
file,
line,
function,
message: extract_panic_message(panic_info),
timestamp,
}
}
pub fn format(&self) -> String {
format!(
"Panic at {}:{}:{} - {}",
self.file, self.line, self.function, self.message
)
}
}
const MAX_PANIC_MESSAGE_LEN: usize = 4096;
pub fn extract_panic_message(panic_info: &dyn Any) -> String {
let msg = if let Some(s) = panic_info.downcast_ref::<String>() {
s.clone()
} else if let Some(s) = panic_info.downcast_ref::<&str>() {
(*s).to_string()
} else {
"Unknown panic payload".to_string()
};
if msg.len() > MAX_PANIC_MESSAGE_LEN {
let truncate_at = msg.floor_char_boundary(MAX_PANIC_MESSAGE_LEN);
format!("{}... [truncated]", &msg[..truncate_at])
} else {
msg
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_panic_message_string() {
let panic_msg = "test panic".to_string();
let msg = extract_panic_message(&panic_msg);
assert_eq!(msg, "test panic");
}
#[test]
fn test_extract_panic_message_str() {
let panic_msg: &str = "test panic";
let msg = extract_panic_message(&panic_msg);
assert_eq!(msg, "test panic");
}
#[test]
fn test_extract_panic_message_unknown() {
let panic_msg = 42i32;
let msg = extract_panic_message(&panic_msg);
assert_eq!(msg, "Unknown panic payload");
}
#[test]
fn test_panic_context_format() {
let panic_msg = "test error".to_string();
let ctx = PanicContext::new("test.rs", 42, "test_function", &panic_msg);
let formatted = ctx.format();
assert!(formatted.contains("test.rs"));
assert!(formatted.contains("42"));
assert!(formatted.contains("test_function"));
assert!(formatted.contains("test error"));
}
#[test]
fn test_panic_message_truncation() {
let long_msg = "x".repeat(5000);
let msg = extract_panic_message(&long_msg);
assert!(msg.len() <= MAX_PANIC_MESSAGE_LEN + 20);
assert!(msg.ends_with("... [truncated]"));
}
#[test]
fn test_panic_message_truncation_utf8_boundary() {
let mut msg = "x".repeat(4093);
msg.push('🦀');
msg.push_str("yyy");
let truncated = extract_panic_message(&msg);
assert!(truncated.ends_with("... [truncated]"));
assert!(std::str::from_utf8(truncated.as_bytes()).is_ok());
assert!(!truncated.contains("🦀"));
assert!(!truncated.contains("yyy"));
}
#[test]
fn test_panic_message_no_truncation_needed() {
let short_msg = "short".to_string();
let msg = extract_panic_message(&short_msg);
assert_eq!(msg, "short");
assert!(!msg.contains("[truncated]"));
}
}