1use crate::{err::PyErrArguments, exceptions, IntoPy, PyErr, PyObject, Python};
2use std::io;
3
4/// Convert `PyErr` to `io::Error`
5impl From<PyErr> for io::Error {
6 fn from(err: PyErr) -> Self {
7 let kind = Python::with_gil(|py| {
8 if err.is_instance_of::<exceptions::PyBrokenPipeError>(py) {
9 io::ErrorKind::BrokenPipe
10 } else if err.is_instance_of::<exceptions::PyConnectionRefusedError>(py) {
11 io::ErrorKind::ConnectionRefused
12 } else if err.is_instance_of::<exceptions::PyConnectionAbortedError>(py) {
13 io::ErrorKind::ConnectionAborted
14 } else if err.is_instance_of::<exceptions::PyConnectionResetError>(py) {
15 io::ErrorKind::ConnectionReset
16 } else if err.is_instance_of::<exceptions::PyInterruptedError>(py) {
17 io::ErrorKind::Interrupted
18 } else if err.is_instance_of::<exceptions::PyFileNotFoundError>(py) {
19 io::ErrorKind::NotFound
20 } else if err.is_instance_of::<exceptions::PyPermissionError>(py) {
21 io::ErrorKind::PermissionDenied
22 } else if err.is_instance_of::<exceptions::PyFileExistsError>(py) {
23 io::ErrorKind::AlreadyExists
24 } else if err.is_instance_of::<exceptions::PyBlockingIOError>(py) {
25 io::ErrorKind::WouldBlock
26 } else if err.is_instance_of::<exceptions::PyTimeoutError>(py) {
27 io::ErrorKind::TimedOut
28 } else {
29 io::ErrorKind::Other
30 }
31 });
32 io::Error::new(kind, err)
33 }
34}
35
36/// Create `PyErr` from `io::Error`
37/// (`OSError` except if the `io::Error` is wrapping a Python exception,
38/// in this case the exception is returned)
39impl From<io::Error> for PyErr {
40 fn from(err: io::Error) -> PyErr {
41 // If the error wraps a Python error we return it
42 if err.get_ref().map_or(default:false, |e: &(dyn Error + Sync + Send)| e.is::<PyErr>()) {
43 return *err.into_inner().unwrap().downcast().unwrap();
44 }
45 match err.kind() {
46 io::ErrorKind::BrokenPipe => exceptions::PyBrokenPipeError::new_err(args:err),
47 io::ErrorKind::ConnectionRefused => exceptions::PyConnectionRefusedError::new_err(args:err),
48 io::ErrorKind::ConnectionAborted => exceptions::PyConnectionAbortedError::new_err(args:err),
49 io::ErrorKind::ConnectionReset => exceptions::PyConnectionResetError::new_err(args:err),
50 io::ErrorKind::Interrupted => exceptions::PyInterruptedError::new_err(args:err),
51 io::ErrorKind::NotFound => exceptions::PyFileNotFoundError::new_err(args:err),
52 io::ErrorKind::PermissionDenied => exceptions::PyPermissionError::new_err(args:err),
53 io::ErrorKind::AlreadyExists => exceptions::PyFileExistsError::new_err(args:err),
54 io::ErrorKind::WouldBlock => exceptions::PyBlockingIOError::new_err(args:err),
55 io::ErrorKind::TimedOut => exceptions::PyTimeoutError::new_err(args:err),
56 _ => exceptions::PyOSError::new_err(args:err),
57 }
58 }
59}
60
61impl PyErrArguments for io::Error {
62 fn arguments(self, py: Python<'_>) -> PyObject {
63 self.to_string().into_py(py)
64 }
65}
66
67impl<W> From<io::IntoInnerError<W>> for PyErr {
68 fn from(err: io::IntoInnerError<W>) -> PyErr {
69 err.into_error().into()
70 }
71}
72
73impl<W: Send + Sync> PyErrArguments for io::IntoInnerError<W> {
74 fn arguments(self, py: Python<'_>) -> PyObject {
75 self.into_error().arguments(py)
76 }
77}
78
79impl From<std::convert::Infallible> for PyErr {
80 fn from(_: std::convert::Infallible) -> PyErr {
81 unreachable!()
82 }
83}
84
85macro_rules! impl_to_pyerr {
86 ($err: ty, $pyexc: ty) => {
87 impl PyErrArguments for $err {
88 fn arguments(self, py: Python<'_>) -> PyObject {
89 self.to_string().into_py(py)
90 }
91 }
92
93 impl std::convert::From<$err> for PyErr {
94 fn from(err: $err) -> PyErr {
95 <$pyexc>::new_err(err)
96 }
97 }
98 };
99}
100
101impl_to_pyerr!(std::array::TryFromSliceError, exceptions::PyValueError);
102impl_to_pyerr!(std::num::ParseIntError, exceptions::PyValueError);
103impl_to_pyerr!(std::num::ParseFloatError, exceptions::PyValueError);
104impl_to_pyerr!(std::num::TryFromIntError, exceptions::PyValueError);
105impl_to_pyerr!(std::str::ParseBoolError, exceptions::PyValueError);
106impl_to_pyerr!(std::ffi::IntoStringError, exceptions::PyUnicodeDecodeError);
107impl_to_pyerr!(std::ffi::NulError, exceptions::PyValueError);
108impl_to_pyerr!(std::str::Utf8Error, exceptions::PyUnicodeDecodeError);
109impl_to_pyerr!(std::string::FromUtf8Error, exceptions::PyUnicodeDecodeError);
110impl_to_pyerr!(
111 std::string::FromUtf16Error,
112 exceptions::PyUnicodeDecodeError
113);
114impl_to_pyerr!(
115 std::char::DecodeUtf16Error,
116 exceptions::PyUnicodeDecodeError
117);
118impl_to_pyerr!(std::net::AddrParseError, exceptions::PyValueError);
119
120#[cfg(test)]
121mod tests {
122 use crate::{PyErr, Python};
123 use std::io;
124
125 #[test]
126 fn io_errors() {
127 let check_err = |kind, expected_ty| {
128 Python::with_gil(|py| {
129 let rust_err = io::Error::new(kind, "some error msg");
130
131 let py_err: PyErr = rust_err.into();
132 let py_err_msg = format!("{}: some error msg", expected_ty);
133 assert_eq!(py_err.to_string(), py_err_msg);
134 let py_error_clone = py_err.clone_ref(py);
135
136 let rust_err_from_py_err: io::Error = py_err.into();
137 assert_eq!(rust_err_from_py_err.to_string(), py_err_msg);
138 assert_eq!(rust_err_from_py_err.kind(), kind);
139
140 let py_err_recovered_from_rust_err: PyErr = rust_err_from_py_err.into();
141 assert!(py_err_recovered_from_rust_err
142 .value(py)
143 .is(py_error_clone.value(py))); // It should be the same exception
144 })
145 };
146
147 check_err(io::ErrorKind::BrokenPipe, "BrokenPipeError");
148 check_err(io::ErrorKind::ConnectionRefused, "ConnectionRefusedError");
149 check_err(io::ErrorKind::ConnectionAborted, "ConnectionAbortedError");
150 check_err(io::ErrorKind::ConnectionReset, "ConnectionResetError");
151 check_err(io::ErrorKind::Interrupted, "InterruptedError");
152 check_err(io::ErrorKind::NotFound, "FileNotFoundError");
153 check_err(io::ErrorKind::PermissionDenied, "PermissionError");
154 check_err(io::ErrorKind::AlreadyExists, "FileExistsError");
155 check_err(io::ErrorKind::WouldBlock, "BlockingIOError");
156 check_err(io::ErrorKind::TimedOut, "TimeoutError");
157 }
158}
159