use fidius_core::PluginError;
use pyo3::prelude::*;
use pyo3::types::PyTraceback;
use serde_json::json;
pub fn pyerr_to_plugin_error(err: PyErr) -> PluginError {
Python::with_gil(|py| {
let value = err.value(py);
let code = value
.getattr("__class__")
.and_then(|cls| cls.getattr("__name__"))
.and_then(|name| name.extract::<String>())
.unwrap_or_else(|_| "UNKNOWN_PYTHON_ERROR".to_string());
let message = value
.str()
.and_then(|s| s.extract::<String>())
.unwrap_or_else(|_| "<unprintable Python exception>".to_string());
let traceback = err
.traceback(py)
.and_then(|tb| format_traceback(py, tb).ok())
.unwrap_or_default();
let details = json!({ "traceback": traceback }).to_string();
PluginError {
code,
message,
details: Some(details),
}
})
}
fn format_traceback(py: Python<'_>, tb: Bound<'_, PyTraceback>) -> PyResult<String> {
let traceback_mod = py.import("traceback")?;
let frames = traceback_mod.call_method1("format_tb", (tb,))?;
let parts: Vec<String> = frames.extract()?;
Ok(parts.join(""))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn maps_value_error_to_plugin_error() {
crate::ensure_initialized();
let err = Python::with_gil(|py| -> PyErr {
py.eval(
std::ffi::CString::new("(_ for _ in ()).throw(ValueError('boom'))")
.unwrap()
.as_c_str(),
None,
None,
)
.unwrap_err()
});
let pe = pyerr_to_plugin_error(err);
assert_eq!(pe.code, "ValueError");
assert!(pe.message.contains("boom"));
let details = pe.details.expect("details should be set");
assert!(details.contains("traceback"));
}
}