spikard_core/
errors.rs

1//! Shared structured error types and panic shielding utilities.
2//!
3//! Bindings should convert all fatal paths into this shape to keep cross-language
4//! error payloads consistent and avoid panics crossing FFI boundaries.
5
6use serde::Serialize;
7use serde_json::Value;
8use std::panic::{UnwindSafe, catch_unwind};
9
10/// Canonical error payload: { error, code, details }.
11#[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
33/// Catch panics and convert to a structured error so they don't cross FFI boundaries.
34pub fn shield_panic<T, F>(f: F) -> Result<T, StructuredError>
35where
36    F: FnOnce() -> T + UnwindSafe,
37{
38    catch_unwind(f).map_err(|_| StructuredError::simple("panic", "Unexpected panic in Rust code"))
39}