aws_smithy_http_server_python/util/
error.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Provides utilities for Python errors.
7
8use std::fmt;
9
10use pyo3::{PyErr, Python};
11
12/// Wraps [PyErr] with a richer debug output that includes traceback and cause.
13pub struct RichPyErr(PyErr);
14
15impl fmt::Debug for RichPyErr {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
17        Python::with_gil(|py| {
18            let mut debug_struct = f.debug_struct("RichPyErr");
19            debug_struct
20                .field("type", self.0.get_type(py))
21                .field("value", self.0.value(py));
22
23            if let Some(traceback) = self.0.traceback(py) {
24                if let Ok(traceback) = traceback.format() {
25                    debug_struct.field("traceback", &traceback);
26                }
27            }
28
29            if let Some(cause) = self.0.cause(py) {
30                debug_struct.field("cause", &rich_py_err(cause));
31            }
32
33            debug_struct.finish()
34        })
35    }
36}
37
38/// Wrap `err` with [RichPyErr] to have a richer debug output.
39pub fn rich_py_err(err: PyErr) -> RichPyErr {
40    RichPyErr(err)
41}
42
43#[cfg(test)]
44mod tests {
45    use pyo3::prelude::*;
46
47    use super::*;
48
49    #[test]
50    fn rich_python_errors() -> PyResult<()> {
51        pyo3::prepare_freethreaded_python();
52
53        let py_err = Python::with_gil(|py| {
54            py.run(
55                r#"
56def foo():
57    base_err = ValueError("base error")
58    raise ValueError("some python error") from base_err
59
60def bar():
61    foo()
62
63def baz():
64    bar()
65
66baz()
67"#,
68                None,
69                None,
70            )
71            .unwrap_err()
72        });
73
74        let debug_output = format!("{:?}", rich_py_err(py_err));
75
76        // Make sure we are capturing error message
77        assert!(debug_output.contains("some python error"));
78
79        // Make sure we are capturing traceback
80        assert!(debug_output.contains("foo"));
81        assert!(debug_output.contains("bar"));
82        assert!(debug_output.contains("baz"));
83
84        // Make sure we are capturing cause
85        assert!(debug_output.contains("base error"));
86
87        Ok(())
88    }
89}