Skip to main content

rustpy_ml/
error.rs

1use thiserror::Error;
2use pyo3::types::PyTracebackMethods;
3use std::ffi::NulError;
4
5/// Main error type for Rustpy
6#[derive(Error, Debug)]
7pub enum Error {
8    /// Python runtime error
9    #[error("Python error: {0}")]
10    Py(#[from] pyo3::PyErr),
11
12    /// Variable interpolation or runtime error
13    #[error("Runtime error: {0}")]
14    Interp(String),
15
16    /// Type conversion error
17    #[error("Type conversion error: {0}")]
18    Conversion(String),
19
20    /// Module import error
21    #[error("Failed to import module '{module}': {reason}")]
22    Import {
23        module: String,
24        reason: String,
25    },
26
27    /// Function call error
28    #[error("Failed to call function '{function}': {reason}")]
29    Call {
30        function: String,
31        reason: String,
32    },
33
34    /// Runtime not initialized
35    #[error("Python runtime not initialized. Call rustpy_ml::init() first.")]
36    NotInitialized,
37
38    /// CString conversion error (null byte in string)
39    #[error("String contains null byte: {0}")]
40    NulByte(#[from] NulError),
41
42    /// Generic error wrapper
43    #[error(transparent)]
44    Anyhow(#[from] anyhow::Error),
45}
46
47impl Error {
48    /// Create a new interpolation error
49    pub fn interp(msg: impl Into<String>) -> Self {
50        Error::Interp(msg.into())
51    }
52
53    /// Create a new conversion error
54    pub fn conversion(msg: impl Into<String>) -> Self {
55        Error::Conversion(msg.into())
56    }
57
58    /// Create a new import error
59    pub fn import(module: impl Into<String>, reason: impl Into<String>) -> Self {
60        Error::Import {
61            module: module.into(),
62            reason: reason.into(),
63        }
64    }
65
66    /// Create a new call error
67    pub fn call(function: impl Into<String>, reason: impl Into<String>) -> Self {
68        Error::Call {
69            function: function.into(),
70            reason: reason.into(),
71        }
72    }
73
74    /// Get the Python traceback if available
75    pub fn traceback(&self) -> Option<String> {
76        match self {
77            Error::Py(err) => {
78                pyo3::Python::with_gil(|py| {
79                    err.traceback(py).and_then(|tb| {
80                        tb.format().ok()
81                    })
82                })
83            }
84            _ => None,
85        }
86    }
87
88    /// Print detailed error information including traceback
89    pub fn print_detailed(&self) {
90        eprintln!("Error: {}", self);
91        if let Some(tb) = self.traceback() {
92            eprintln!("Traceback:\n{}", tb);
93        }
94    }
95}
96
97/// Result type alias for Rustpy operations
98pub type Result<T> = std::result::Result<T, Error>;
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_error_creation() {
106        let err = Error::interp("test error");
107        assert!(matches!(err, Error::Interp(_)));
108
109        let err = Error::import("numpy", "not found");
110        assert!(matches!(err, Error::Import { .. }));
111
112        let err = Error::call("test_func", "invalid args");
113        assert!(matches!(err, Error::Call { .. }));
114    }
115}