pyo3/err/
impls.rs

1use crate::{err::PyErrArguments, exceptions, PyErr, Python};
2use crate::{IntoPyObject, Py, PyAny};
3use std::io;
4
5/// Convert `PyErr` to `io::Error`
6impl From<PyErr> for io::Error {
7    fn from(err: PyErr) -> Self {
8        let kind = Python::attach(|py| {
9            if err.is_instance_of::<exceptions::PyBrokenPipeError>(py) {
10                io::ErrorKind::BrokenPipe
11            } else if err.is_instance_of::<exceptions::PyConnectionRefusedError>(py) {
12                io::ErrorKind::ConnectionRefused
13            } else if err.is_instance_of::<exceptions::PyConnectionAbortedError>(py) {
14                io::ErrorKind::ConnectionAborted
15            } else if err.is_instance_of::<exceptions::PyConnectionResetError>(py) {
16                io::ErrorKind::ConnectionReset
17            } else if err.is_instance_of::<exceptions::PyInterruptedError>(py) {
18                io::ErrorKind::Interrupted
19            } else if err.is_instance_of::<exceptions::PyFileNotFoundError>(py) {
20                io::ErrorKind::NotFound
21            } else if err.is_instance_of::<exceptions::PyPermissionError>(py) {
22                io::ErrorKind::PermissionDenied
23            } else if err.is_instance_of::<exceptions::PyFileExistsError>(py) {
24                io::ErrorKind::AlreadyExists
25            } else if err.is_instance_of::<exceptions::PyBlockingIOError>(py) {
26                io::ErrorKind::WouldBlock
27            } else if err.is_instance_of::<exceptions::PyTimeoutError>(py) {
28                io::ErrorKind::TimedOut
29            } else if err.is_instance_of::<exceptions::PyMemoryError>(py) {
30                io::ErrorKind::OutOfMemory
31            } else if err.is_instance_of::<exceptions::PyIsADirectoryError>(py) {
32                io::ErrorKind::IsADirectory
33            } else if err.is_instance_of::<exceptions::PyNotADirectoryError>(py) {
34                io::ErrorKind::NotADirectory
35            } else {
36                io::ErrorKind::Other
37            }
38        });
39        io::Error::new(kind, err)
40    }
41}
42
43/// Create `PyErr` from `io::Error`
44/// (`OSError` except if the `io::Error` is wrapping a Python exception,
45/// in this case the exception is returned)
46impl From<io::Error> for PyErr {
47    fn from(err: io::Error) -> PyErr {
48        // If the error wraps a Python error we return it
49        if err.get_ref().is_some_and(|e| e.is::<PyErr>()) {
50            return *err.into_inner().unwrap().downcast().unwrap();
51        }
52        match err.kind() {
53            io::ErrorKind::BrokenPipe => exceptions::PyBrokenPipeError::new_err(err),
54            io::ErrorKind::ConnectionRefused => exceptions::PyConnectionRefusedError::new_err(err),
55            io::ErrorKind::ConnectionAborted => exceptions::PyConnectionAbortedError::new_err(err),
56            io::ErrorKind::ConnectionReset => exceptions::PyConnectionResetError::new_err(err),
57            io::ErrorKind::Interrupted => exceptions::PyInterruptedError::new_err(err),
58            io::ErrorKind::NotFound => exceptions::PyFileNotFoundError::new_err(err),
59            io::ErrorKind::PermissionDenied => exceptions::PyPermissionError::new_err(err),
60            io::ErrorKind::AlreadyExists => exceptions::PyFileExistsError::new_err(err),
61            io::ErrorKind::WouldBlock => exceptions::PyBlockingIOError::new_err(err),
62            io::ErrorKind::TimedOut => exceptions::PyTimeoutError::new_err(err),
63            io::ErrorKind::OutOfMemory => exceptions::PyMemoryError::new_err(err),
64            io::ErrorKind::IsADirectory => exceptions::PyIsADirectoryError::new_err(err),
65            io::ErrorKind::NotADirectory => exceptions::PyNotADirectoryError::new_err(err),
66            _ => exceptions::PyOSError::new_err(err),
67        }
68    }
69}
70
71impl PyErrArguments for io::Error {
72    fn arguments(self, py: Python<'_>) -> Py<PyAny> {
73        //FIXME(icxolu) remove unwrap
74        self.to_string()
75            .into_pyobject(py)
76            .unwrap()
77            .into_any()
78            .unbind()
79    }
80}
81
82impl<W> From<io::IntoInnerError<W>> for PyErr {
83    fn from(err: io::IntoInnerError<W>) -> PyErr {
84        err.into_error().into()
85    }
86}
87
88impl<W: Send + Sync> PyErrArguments for io::IntoInnerError<W> {
89    fn arguments(self, py: Python<'_>) -> Py<PyAny> {
90        self.into_error().arguments(py)
91    }
92}
93
94impl From<std::convert::Infallible> for PyErr {
95    fn from(_: std::convert::Infallible) -> PyErr {
96        unreachable!()
97    }
98}
99
100macro_rules! impl_to_pyerr {
101    ($err: ty, $pyexc: ty) => {
102        impl PyErrArguments for $err {
103            fn arguments(self, py: Python<'_>) -> $crate::Py<$crate::PyAny> {
104                // FIXME(icxolu) remove unwrap
105                self.to_string()
106                    .into_pyobject(py)
107                    .unwrap()
108                    .into_any()
109                    .unbind()
110            }
111        }
112
113        impl std::convert::From<$err> for PyErr {
114            fn from(err: $err) -> PyErr {
115                <$pyexc>::new_err(err)
116            }
117        }
118    };
119}
120
121impl_to_pyerr!(std::array::TryFromSliceError, exceptions::PyValueError);
122impl_to_pyerr!(std::num::ParseIntError, exceptions::PyValueError);
123impl_to_pyerr!(std::num::ParseFloatError, exceptions::PyValueError);
124impl_to_pyerr!(std::num::TryFromIntError, exceptions::PyValueError);
125impl_to_pyerr!(std::str::ParseBoolError, exceptions::PyValueError);
126impl_to_pyerr!(std::ffi::IntoStringError, exceptions::PyUnicodeDecodeError);
127impl_to_pyerr!(std::ffi::NulError, exceptions::PyValueError);
128impl_to_pyerr!(std::str::Utf8Error, exceptions::PyUnicodeDecodeError);
129impl_to_pyerr!(std::string::FromUtf8Error, exceptions::PyUnicodeDecodeError);
130impl_to_pyerr!(
131    std::string::FromUtf16Error,
132    exceptions::PyUnicodeDecodeError
133);
134impl_to_pyerr!(
135    std::char::DecodeUtf16Error,
136    exceptions::PyUnicodeDecodeError
137);
138impl_to_pyerr!(std::net::AddrParseError, exceptions::PyValueError);
139
140#[cfg(test)]
141mod tests {
142    use crate::{PyErr, Python};
143    use std::io;
144
145    #[test]
146    fn io_errors() {
147        use crate::types::any::PyAnyMethods;
148
149        let check_err = |kind, expected_ty| {
150            Python::attach(|py| {
151                let rust_err = io::Error::new(kind, "some error msg");
152
153                let py_err: PyErr = rust_err.into();
154                let py_err_msg = format!("{expected_ty}: some error msg");
155                assert_eq!(py_err.to_string(), py_err_msg);
156                let py_error_clone = py_err.clone_ref(py);
157
158                let rust_err_from_py_err: io::Error = py_err.into();
159                assert_eq!(rust_err_from_py_err.to_string(), py_err_msg);
160                assert_eq!(rust_err_from_py_err.kind(), kind);
161
162                let py_err_recovered_from_rust_err: PyErr = rust_err_from_py_err.into();
163                assert!(py_err_recovered_from_rust_err
164                    .value(py)
165                    .is(py_error_clone.value(py))); // It should be the same exception
166            })
167        };
168
169        check_err(io::ErrorKind::BrokenPipe, "BrokenPipeError");
170        check_err(io::ErrorKind::ConnectionRefused, "ConnectionRefusedError");
171        check_err(io::ErrorKind::ConnectionAborted, "ConnectionAbortedError");
172        check_err(io::ErrorKind::ConnectionReset, "ConnectionResetError");
173        check_err(io::ErrorKind::Interrupted, "InterruptedError");
174        check_err(io::ErrorKind::NotFound, "FileNotFoundError");
175        check_err(io::ErrorKind::PermissionDenied, "PermissionError");
176        check_err(io::ErrorKind::AlreadyExists, "FileExistsError");
177        check_err(io::ErrorKind::WouldBlock, "BlockingIOError");
178        check_err(io::ErrorKind::TimedOut, "TimeoutError");
179        check_err(io::ErrorKind::IsADirectory, "IsADirectoryError");
180        check_err(io::ErrorKind::NotADirectory, "NotADirectoryError");
181    }
182}