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