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#[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#[cfg(wip_feature_std)]
50impl From<io::Error> for PyErr {
51 fn from(err: io::Error) -> PyErr {
52 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 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 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 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 ); })
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 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 #[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 #[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 #[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 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 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}