Skip to main content

pyo3/err/
impls.rs

1use crate::platform::prelude::*;
2use crate::{IntoPyObject, Py, PyAny};
3use crate::{PyErr, Python, err::PyErrArguments, exceptions, types};
4#[cfg(wip_feature_std)]
5use std::io;
6
7/// Convert `PyErr` to `io::Error`
8#[cfg(wip_feature_std)]
9impl From<PyErr> for io::Error {
10    fn from(err: PyErr) -> Self {
11        let kind = Python::attach(|py| {
12            if err.is_instance_of::<exceptions::PyBrokenPipeError>(py) {
13                io::ErrorKind::BrokenPipe
14            } else if err.is_instance_of::<exceptions::PyConnectionRefusedError>(py) {
15                io::ErrorKind::ConnectionRefused
16            } else if err.is_instance_of::<exceptions::PyConnectionAbortedError>(py) {
17                io::ErrorKind::ConnectionAborted
18            } else if err.is_instance_of::<exceptions::PyConnectionResetError>(py) {
19                io::ErrorKind::ConnectionReset
20            } else if err.is_instance_of::<exceptions::PyInterruptedError>(py) {
21                io::ErrorKind::Interrupted
22            } else if err.is_instance_of::<exceptions::PyFileNotFoundError>(py) {
23                io::ErrorKind::NotFound
24            } else if err.is_instance_of::<exceptions::PyPermissionError>(py) {
25                io::ErrorKind::PermissionDenied
26            } else if err.is_instance_of::<exceptions::PyFileExistsError>(py) {
27                io::ErrorKind::AlreadyExists
28            } else if err.is_instance_of::<exceptions::PyBlockingIOError>(py) {
29                io::ErrorKind::WouldBlock
30            } else if err.is_instance_of::<exceptions::PyTimeoutError>(py) {
31                io::ErrorKind::TimedOut
32            } else if err.is_instance_of::<exceptions::PyMemoryError>(py) {
33                io::ErrorKind::OutOfMemory
34            } else 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        });
42        io::Error::new(kind, err)
43    }
44}
45
46/// Create `PyErr` from `io::Error`
47/// (`OSError` except if the `io::Error` is wrapping a Python exception,
48/// in this case the exception is returned)
49#[cfg(wip_feature_std)]
50impl From<io::Error> for PyErr {
51    fn from(err: io::Error) -> PyErr {
52        // If the error wraps a Python error we return it
53        if err.get_ref().is_some_and(|e| e.is::<PyErr>()) {
54            return *err.into_inner().unwrap().downcast().unwrap();
55        }
56        match err.kind() {
57            io::ErrorKind::BrokenPipe => exceptions::PyBrokenPipeError::new_err(err),
58            io::ErrorKind::ConnectionRefused => exceptions::PyConnectionRefusedError::new_err(err),
59            io::ErrorKind::ConnectionAborted => exceptions::PyConnectionAbortedError::new_err(err),
60            io::ErrorKind::ConnectionReset => exceptions::PyConnectionResetError::new_err(err),
61            io::ErrorKind::Interrupted => exceptions::PyInterruptedError::new_err(err),
62            io::ErrorKind::NotFound => exceptions::PyFileNotFoundError::new_err(err),
63            io::ErrorKind::PermissionDenied => exceptions::PyPermissionError::new_err(err),
64            io::ErrorKind::AlreadyExists => exceptions::PyFileExistsError::new_err(err),
65            io::ErrorKind::WouldBlock => exceptions::PyBlockingIOError::new_err(err),
66            io::ErrorKind::TimedOut => exceptions::PyTimeoutError::new_err(err),
67            io::ErrorKind::OutOfMemory => exceptions::PyMemoryError::new_err(err),
68            io::ErrorKind::IsADirectory => exceptions::PyIsADirectoryError::new_err(err),
69            io::ErrorKind::NotADirectory => exceptions::PyNotADirectoryError::new_err(err),
70            _ => exceptions::PyOSError::new_err(err),
71        }
72    }
73}
74
75#[cfg(wip_feature_std)]
76impl PyErrArguments for io::Error {
77    fn arguments(self, py: Python<'_>) -> Py<PyAny> {
78        //FIXME(icxolu) remove unwrap
79        self.to_string()
80            .into_pyobject(py)
81            .unwrap()
82            .into_any()
83            .unbind()
84    }
85}
86
87#[cfg(wip_feature_std)]
88impl<W> From<io::IntoInnerError<W>> for PyErr {
89    fn from(err: io::IntoInnerError<W>) -> PyErr {
90        err.into_error().into()
91    }
92}
93
94#[cfg(wip_feature_std)]
95impl<W: Send + Sync> PyErrArguments for io::IntoInnerError<W> {
96    fn arguments(self, py: Python<'_>) -> Py<PyAny> {
97        self.into_error().arguments(py)
98    }
99}
100
101impl From<core::convert::Infallible> for PyErr {
102    fn from(_: core::convert::Infallible) -> PyErr {
103        unreachable!()
104    }
105}
106
107macro_rules! impl_to_pyerr {
108    ($err: ty, $pyexc: ty) => {
109        impl PyErrArguments for $err {
110            fn arguments(self, py: Python<'_>) -> $crate::Py<$crate::PyAny> {
111                // FIXME(icxolu) remove unwrap
112                self.to_string()
113                    .into_pyobject(py)
114                    .unwrap()
115                    .into_any()
116                    .unbind()
117            }
118        }
119
120        impl core::convert::From<$err> for PyErr {
121            fn from(err: $err) -> PyErr {
122                <$pyexc>::new_err(err)
123            }
124        }
125    };
126}
127
128struct Utf8ErrorWithBytes {
129    err: core::str::Utf8Error,
130    bytes: Vec<u8>,
131}
132
133impl PyErrArguments for Utf8ErrorWithBytes {
134    fn arguments(self, py: Python<'_>) -> Py<PyAny> {
135        let Self { err, bytes } = self;
136        let start = err.valid_up_to();
137        let end = err.error_len().map_or(bytes.len(), |l| start + l);
138
139        let encoding = types::PyString::new(py, "utf-8").into_any();
140        let bytes = types::PyBytes::new(py, &bytes).into_any();
141        let start = types::PyInt::new(py, start).into_any();
142        let end = types::PyInt::new(py, end).into_any();
143        let reason = types::PyString::new(py, "invalid utf-8").into_any();
144
145        // FIXME(icxolu) remove unwrap
146        types::PyTuple::new(py, &[encoding, bytes, start, end, reason])
147            .unwrap()
148            .into_any()
149            .unbind()
150    }
151}
152
153impl PyErrArguments for alloc::string::FromUtf8Error {
154    fn arguments(self, py: Python<'_>) -> Py<PyAny> {
155        Utf8ErrorWithBytes {
156            err: self.utf8_error(),
157            bytes: self.into_bytes(),
158        }
159        .arguments(py)
160    }
161}
162
163impl core::convert::From<alloc::string::FromUtf8Error> for PyErr {
164    fn from(err: alloc::string::FromUtf8Error) -> PyErr {
165        exceptions::PyUnicodeDecodeError::new_err(err)
166    }
167}
168
169impl PyErrArguments for alloc::ffi::IntoStringError {
170    fn arguments(self, py: Python<'_>) -> Py<PyAny> {
171        Utf8ErrorWithBytes {
172            err: self.utf8_error(),
173            bytes: self.into_cstring().into_bytes(),
174        }
175        .arguments(py)
176    }
177}
178
179impl core::convert::From<alloc::ffi::IntoStringError> for PyErr {
180    fn from(err: alloc::ffi::IntoStringError) -> PyErr {
181        exceptions::PyUnicodeDecodeError::new_err(err)
182    }
183}
184
185impl_to_pyerr!(core::array::TryFromSliceError, exceptions::PyValueError);
186impl_to_pyerr!(core::num::ParseIntError, exceptions::PyValueError);
187impl_to_pyerr!(core::num::ParseFloatError, exceptions::PyValueError);
188impl_to_pyerr!(core::num::TryFromIntError, exceptions::PyValueError);
189impl_to_pyerr!(core::str::ParseBoolError, exceptions::PyValueError);
190impl_to_pyerr!(alloc::ffi::NulError, exceptions::PyValueError);
191impl_to_pyerr!(core::net::AddrParseError, exceptions::PyValueError);
192impl_to_pyerr!(core::time::TryFromFloatSecsError, exceptions::PyValueError);
193#[cfg(wip_feature_std)]
194impl_to_pyerr!(std::time::SystemTimeError, exceptions::PyValueError);
195#[cfg(wip_feature_std)]
196impl_to_pyerr!(std::path::StripPrefixError, exceptions::PyValueError);
197#[cfg(wip_feature_std)]
198impl_to_pyerr!(std::env::JoinPathsError, exceptions::PyValueError);
199impl_to_pyerr!(core::char::ParseCharError, exceptions::PyValueError);
200impl_to_pyerr!(core::char::CharTryFromError, exceptions::PyValueError);
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    use crate::exceptions::PyUnicodeDecodeError;
207    use crate::types::PyAnyMethods;
208    use crate::{IntoPyObjectExt as _, PyErr, Python};
209    #[cfg(wip_feature_std)]
210    use std::io;
211
212    #[test]
213    #[cfg(wip_feature_std)]
214    fn io_errors() {
215        use crate::types::any::PyAnyMethods;
216
217        let check_err = |kind, expected_ty| {
218            Python::attach(|py| {
219                let rust_err = io::Error::new(kind, "some error msg");
220
221                let py_err: PyErr = rust_err.into();
222                let py_err_msg = format!("{expected_ty}: some error msg");
223                assert_eq!(py_err.to_string(), py_err_msg);
224                let py_error_clone = py_err.clone_ref(py);
225
226                let rust_err_from_py_err: io::Error = py_err.into();
227                assert_eq!(rust_err_from_py_err.to_string(), py_err_msg);
228                assert_eq!(rust_err_from_py_err.kind(), kind);
229
230                let py_err_recovered_from_rust_err: PyErr = rust_err_from_py_err.into();
231                assert!(
232                    py_err_recovered_from_rust_err
233                        .value(py)
234                        .is(py_error_clone.value(py))
235                ); // It should be the same exception
236            })
237        };
238
239        check_err(io::ErrorKind::BrokenPipe, "BrokenPipeError");
240        check_err(io::ErrorKind::ConnectionRefused, "ConnectionRefusedError");
241        check_err(io::ErrorKind::ConnectionAborted, "ConnectionAbortedError");
242        check_err(io::ErrorKind::ConnectionReset, "ConnectionResetError");
243        check_err(io::ErrorKind::Interrupted, "InterruptedError");
244        check_err(io::ErrorKind::NotFound, "FileNotFoundError");
245        check_err(io::ErrorKind::PermissionDenied, "PermissionError");
246        check_err(io::ErrorKind::AlreadyExists, "FileExistsError");
247        check_err(io::ErrorKind::WouldBlock, "BlockingIOError");
248        check_err(io::ErrorKind::TimedOut, "TimeoutError");
249        check_err(io::ErrorKind::IsADirectory, "IsADirectoryError");
250        check_err(io::ErrorKind::NotADirectory, "NotADirectoryError");
251    }
252
253    #[test]
254    #[allow(invalid_from_utf8)]
255    fn utf8_errors() {
256        let bytes = b"abc\xffdef".to_vec();
257
258        let check_err = |py_err: PyErr| {
259            Python::attach(|py| {
260                let py_err = py_err.into_bound_py_any(py).unwrap();
261
262                assert!(py_err.is_instance_of::<exceptions::PyUnicodeDecodeError>());
263                assert_eq!(
264                    py_err
265                        .getattr("encoding")
266                        .unwrap()
267                        .extract::<String>()
268                        .unwrap(),
269                    "utf-8"
270                );
271                assert_eq!(
272                    py_err
273                        .getattr("object")
274                        .unwrap()
275                        .extract::<Vec<u8>>()
276                        .unwrap(),
277                    &*bytes
278                );
279                assert_eq!(
280                    py_err.getattr("start").unwrap().extract::<usize>().unwrap(),
281                    3
282                );
283                assert_eq!(
284                    py_err.getattr("end").unwrap().extract::<usize>().unwrap(),
285                    4
286                );
287                assert_eq!(
288                    py_err
289                        .getattr("reason")
290                        .unwrap()
291                        .extract::<String>()
292                        .unwrap(),
293                    "invalid utf-8"
294                );
295            });
296        };
297
298        let utf8_err_with_bytes = PyUnicodeDecodeError::new_err(Utf8ErrorWithBytes {
299            err: core::str::from_utf8(&bytes).expect_err("\\xff is invalid utf-8"),
300            bytes: bytes.clone(),
301        });
302        check_err(utf8_err_with_bytes);
303
304        let from_utf8_err = String::from_utf8(bytes.clone())
305            .expect_err("\\xff is invalid utf-8")
306            .into();
307        check_err(from_utf8_err);
308
309        let from_utf8_err = alloc::ffi::CString::new(bytes.clone())
310            .unwrap()
311            .into_string()
312            .expect_err("\\xff is invalid utf-8")
313            .into();
314        check_err(from_utf8_err);
315    }
316
317    #[test]
318    fn std_error_conversions() {
319        Python::attach(|py| {
320            let check_err = |err: PyErr, expected_msg: &str| {
321                let py_err = err.into_bound_py_any(py).unwrap();
322                assert!(py_err.is_instance_of::<exceptions::PyValueError>());
323                let msg = py_err.str().unwrap().to_string();
324                assert_eq!(msg, expected_msg);
325            };
326
327            // TryFromFloatSecsError
328            let float_secs_err = core::time::Duration::try_from_secs_f32(-1.0).unwrap_err();
329            let expected = float_secs_err.to_string();
330            check_err(float_secs_err.into(), &expected);
331
332            // SystemTimeError
333            #[cfg(wip_feature_std)]
334            {
335                let sys_time_err = std::time::SystemTime::now()
336                    .duration_since(
337                        std::time::SystemTime::now() + core::time::Duration::from_secs(1),
338                    )
339                    .unwrap_err();
340                let expected = sys_time_err.to_string();
341                check_err(sys_time_err.into(), &expected);
342            }
343
344            // StripPrefixError
345            #[cfg(wip_feature_std)]
346            {
347                let strip_prefix_err = std::path::Path::new("/a/b/c")
348                    .strip_prefix("/x/y/z")
349                    .unwrap_err();
350                let expected = strip_prefix_err.to_string();
351                check_err(strip_prefix_err.into(), &expected);
352            }
353
354            // JoinPathsError
355            #[cfg(wip_feature_std)]
356            {
357                let join_paths_err =
358                    std::env::join_paths(["a:b", "a;b", "a\"b"].iter()).unwrap_err();
359                let expected = join_paths_err.to_string();
360                check_err(join_paths_err.into(), &expected);
361            }
362
363            // ParseCharError
364            let parse_char_err = "abc".parse::<char>().unwrap_err();
365            let expected = parse_char_err.to_string();
366            check_err(parse_char_err.into(), &expected);
367
368            // CharTryFromError
369            let char_try_from_err = char::try_from(0xD800_u32).unwrap_err();
370            let expected = char_try_from_err.to_string();
371            check_err(char_try_from_err.into(), &expected);
372        });
373    }
374}