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