1use crate::{err::PyErrArguments, exceptions, types, PyErr, Python};
2use crate::{IntoPyObject, Py, PyAny};
3use std::io;
4
5impl 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
43impl From<io::Error> for PyErr {
47 fn from(err: io::Error) -> PyErr {
48 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 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 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 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))); })
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}