Skip to main content

pyo3/err/
impls.rs

1use crate::{err::PyErrArguments, exceptions, types, 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
121struct Utf8ErrorWithBytes {
122    err: std::str::Utf8Error,
123    bytes: Vec<u8>,
124}
125
126impl PyErrArguments for Utf8ErrorWithBytes {
127    fn arguments(self, py: Python<'_>) -> Py<PyAny> {
128        let Self { err, bytes } = self;
129        let start = err.valid_up_to();
130        let end = err.error_len().map_or(bytes.len(), |l| start + l);
131
132        let encoding = types::PyString::new(py, "utf-8").into_any();
133        let bytes = types::PyBytes::new(py, &bytes).into_any();
134        let start = types::PyInt::new(py, start).into_any();
135        let end = types::PyInt::new(py, end).into_any();
136        let reason = types::PyString::new(py, "invalid utf-8").into_any();
137
138        // FIXME(icxolu) remove unwrap
139        types::PyTuple::new(py, &[encoding, bytes, start, end, reason])
140            .unwrap()
141            .into_any()
142            .unbind()
143    }
144}
145
146impl PyErrArguments for std::string::FromUtf8Error {
147    fn arguments(self, py: Python<'_>) -> Py<PyAny> {
148        Utf8ErrorWithBytes {
149            err: self.utf8_error(),
150            bytes: self.into_bytes(),
151        }
152        .arguments(py)
153    }
154}
155
156impl std::convert::From<std::string::FromUtf8Error> for PyErr {
157    fn from(err: std::string::FromUtf8Error) -> PyErr {
158        exceptions::PyUnicodeDecodeError::new_err(err)
159    }
160}
161
162impl PyErrArguments for std::ffi::IntoStringError {
163    fn arguments(self, py: Python<'_>) -> Py<PyAny> {
164        Utf8ErrorWithBytes {
165            err: self.utf8_error(),
166            bytes: self.into_cstring().into_bytes(),
167        }
168        .arguments(py)
169    }
170}
171
172impl std::convert::From<std::ffi::IntoStringError> for PyErr {
173    fn from(err: std::ffi::IntoStringError) -> PyErr {
174        exceptions::PyUnicodeDecodeError::new_err(err)
175    }
176}
177
178impl_to_pyerr!(std::array::TryFromSliceError, exceptions::PyValueError);
179impl_to_pyerr!(std::num::ParseIntError, exceptions::PyValueError);
180impl_to_pyerr!(std::num::ParseFloatError, exceptions::PyValueError);
181impl_to_pyerr!(std::num::TryFromIntError, exceptions::PyValueError);
182impl_to_pyerr!(std::str::ParseBoolError, exceptions::PyValueError);
183impl_to_pyerr!(std::ffi::NulError, exceptions::PyValueError);
184impl_to_pyerr!(std::net::AddrParseError, exceptions::PyValueError);
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    use crate::exceptions::PyUnicodeDecodeError;
191    use crate::types::PyAnyMethods;
192    use crate::{IntoPyObjectExt as _, PyErr, Python};
193    use std::io;
194
195    #[test]
196    fn io_errors() {
197        use crate::types::any::PyAnyMethods;
198
199        let check_err = |kind, expected_ty| {
200            Python::attach(|py| {
201                let rust_err = io::Error::new(kind, "some error msg");
202
203                let py_err: PyErr = rust_err.into();
204                let py_err_msg = format!("{expected_ty}: some error msg");
205                assert_eq!(py_err.to_string(), py_err_msg);
206                let py_error_clone = py_err.clone_ref(py);
207
208                let rust_err_from_py_err: io::Error = py_err.into();
209                assert_eq!(rust_err_from_py_err.to_string(), py_err_msg);
210                assert_eq!(rust_err_from_py_err.kind(), kind);
211
212                let py_err_recovered_from_rust_err: PyErr = rust_err_from_py_err.into();
213                assert!(py_err_recovered_from_rust_err
214                    .value(py)
215                    .is(py_error_clone.value(py))); // It should be the same exception
216            })
217        };
218
219        check_err(io::ErrorKind::BrokenPipe, "BrokenPipeError");
220        check_err(io::ErrorKind::ConnectionRefused, "ConnectionRefusedError");
221        check_err(io::ErrorKind::ConnectionAborted, "ConnectionAbortedError");
222        check_err(io::ErrorKind::ConnectionReset, "ConnectionResetError");
223        check_err(io::ErrorKind::Interrupted, "InterruptedError");
224        check_err(io::ErrorKind::NotFound, "FileNotFoundError");
225        check_err(io::ErrorKind::PermissionDenied, "PermissionError");
226        check_err(io::ErrorKind::AlreadyExists, "FileExistsError");
227        check_err(io::ErrorKind::WouldBlock, "BlockingIOError");
228        check_err(io::ErrorKind::TimedOut, "TimeoutError");
229        check_err(io::ErrorKind::IsADirectory, "IsADirectoryError");
230        check_err(io::ErrorKind::NotADirectory, "NotADirectoryError");
231    }
232
233    #[test]
234    #[allow(invalid_from_utf8)]
235    fn utf8_errors() {
236        let bytes = b"abc\xffdef".to_vec();
237
238        let check_err = |py_err: PyErr| {
239            Python::attach(|py| {
240                let py_err = py_err.into_bound_py_any(py).unwrap();
241
242                assert!(py_err.is_instance_of::<exceptions::PyUnicodeDecodeError>());
243                assert_eq!(
244                    py_err
245                        .getattr("encoding")
246                        .unwrap()
247                        .extract::<String>()
248                        .unwrap(),
249                    "utf-8"
250                );
251                assert_eq!(
252                    py_err
253                        .getattr("object")
254                        .unwrap()
255                        .extract::<Vec<u8>>()
256                        .unwrap(),
257                    &*bytes
258                );
259                assert_eq!(
260                    py_err.getattr("start").unwrap().extract::<usize>().unwrap(),
261                    3
262                );
263                assert_eq!(
264                    py_err.getattr("end").unwrap().extract::<usize>().unwrap(),
265                    4
266                );
267                assert_eq!(
268                    py_err
269                        .getattr("reason")
270                        .unwrap()
271                        .extract::<String>()
272                        .unwrap(),
273                    "invalid utf-8"
274                );
275            });
276        };
277
278        let utf8_err_with_bytes = PyUnicodeDecodeError::new_err(Utf8ErrorWithBytes {
279            err: std::str::from_utf8(&bytes).expect_err("\\xff is invalid utf-8"),
280            bytes: bytes.clone(),
281        });
282        check_err(utf8_err_with_bytes);
283
284        let from_utf8_err = String::from_utf8(bytes.clone())
285            .expect_err("\\xff is invalid utf-8")
286            .into();
287        check_err(from_utf8_err);
288
289        let from_utf8_err = std::ffi::CString::new(bytes.clone())
290            .unwrap()
291            .into_string()
292            .expect_err("\\xff is invalid utf-8")
293            .into();
294        check_err(from_utf8_err);
295    }
296}