1use serde::Serialize;
7use serde_json::Value;
8use std::panic::{UnwindSafe, catch_unwind};
9
10#[derive(Debug, Clone, Serialize)]
12pub struct StructuredError {
13 pub error: String,
14 pub code: String,
15 #[serde(default)]
16 pub details: Value,
17}
18
19impl StructuredError {
20 pub fn new(code: impl Into<String>, error: impl Into<String>, details: Value) -> Self {
21 Self {
22 code: code.into(),
23 error: error.into(),
24 details,
25 }
26 }
27
28 pub fn simple(code: impl Into<String>, error: impl Into<String>) -> Self {
29 Self::new(code, error, Value::Object(serde_json::Map::new()))
30 }
31}
32
33pub fn shield_panic<T, F>(f: F) -> Result<T, StructuredError>
38where
39 F: FnOnce() -> T + UnwindSafe,
40{
41 catch_unwind(f).map_err(|_| StructuredError::simple("panic", "Unexpected panic in Rust code"))
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47 use serde_json::json;
48
49 #[test]
50 fn structured_error_constructors_populate_fields() {
51 let details = json!({"field": "name"});
52 let err = StructuredError::new("invalid", "bad input", details.clone());
53 assert_eq!(err.code, "invalid");
54 assert_eq!(err.error, "bad input");
55 assert_eq!(err.details, details);
56
57 let simple = StructuredError::simple("missing", "not found");
58 assert_eq!(simple.code, "missing");
59 assert_eq!(simple.error, "not found");
60 assert!(simple.details.is_object());
61 }
62
63 #[test]
64 fn shield_panic_returns_ok_or_structured_error() {
65 let ok = shield_panic(|| 42);
66 assert_eq!(ok.unwrap(), 42);
67
68 let err = shield_panic(|| panic!("boom")).unwrap_err();
69 assert_eq!(err.code, "panic");
70 assert!(err.error.contains("Unexpected panic"));
71 }
72}