1 | use crate::IntoPyObject; |
2 | use crate::{err::PyErrArguments, exceptions, PyErr, PyObject, Python}; |
3 | use std::io; |
4 | |
5 | /// Convert `PyErr` to `io::Error` |
6 | impl From<PyErr> for io::Error { |
7 | fn from(err: PyErr) -> Self { |
8 | let kind = Python::with_gil(|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 { |
30 | #[cfg (io_error_more)] |
31 | 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 | #[cfg (not(io_error_more))] |
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 | impl From<io::Error> for PyErr { |
50 | fn from(err: io::Error) -> PyErr { |
51 | // If the error wraps a Python error we return it |
52 | if err.get_ref().map_or(false, |e| e.is::<PyErr>()) { |
53 | return *err.into_inner().unwrap().downcast().unwrap(); |
54 | } |
55 | match err.kind() { |
56 | io::ErrorKind::BrokenPipe => exceptions::PyBrokenPipeError::new_err(err), |
57 | io::ErrorKind::ConnectionRefused => exceptions::PyConnectionRefusedError::new_err(err), |
58 | io::ErrorKind::ConnectionAborted => exceptions::PyConnectionAbortedError::new_err(err), |
59 | io::ErrorKind::ConnectionReset => exceptions::PyConnectionResetError::new_err(err), |
60 | io::ErrorKind::Interrupted => exceptions::PyInterruptedError::new_err(err), |
61 | io::ErrorKind::NotFound => exceptions::PyFileNotFoundError::new_err(err), |
62 | io::ErrorKind::PermissionDenied => exceptions::PyPermissionError::new_err(err), |
63 | io::ErrorKind::AlreadyExists => exceptions::PyFileExistsError::new_err(err), |
64 | io::ErrorKind::WouldBlock => exceptions::PyBlockingIOError::new_err(err), |
65 | io::ErrorKind::TimedOut => exceptions::PyTimeoutError::new_err(err), |
66 | #[cfg (io_error_more)] |
67 | io::ErrorKind::IsADirectory => exceptions::PyIsADirectoryError::new_err(err), |
68 | #[cfg (io_error_more)] |
69 | io::ErrorKind::NotADirectory => exceptions::PyNotADirectoryError::new_err(err), |
70 | _ => exceptions::PyOSError::new_err(err), |
71 | } |
72 | } |
73 | } |
74 | |
75 | impl PyErrArguments for io::Error { |
76 | fn arguments(self, py: Python<'_>) -> PyObject { |
77 | //FIXME(icxolu) remove unwrap |
78 | self.to_string() |
79 | .into_pyobject(py) |
80 | .unwrap() |
81 | .into_any() |
82 | .unbind() |
83 | } |
84 | } |
85 | |
86 | impl<W> From<io::IntoInnerError<W>> for PyErr { |
87 | fn from(err: io::IntoInnerError<W>) -> PyErr { |
88 | err.into_error().into() |
89 | } |
90 | } |
91 | |
92 | impl<W: Send + Sync> PyErrArguments for io::IntoInnerError<W> { |
93 | fn arguments(self, py: Python<'_>) -> PyObject { |
94 | self.into_error().arguments(py) |
95 | } |
96 | } |
97 | |
98 | impl From<std::convert::Infallible> for PyErr { |
99 | fn from(_: std::convert::Infallible) -> PyErr { |
100 | unreachable!() |
101 | } |
102 | } |
103 | |
104 | macro_rules! impl_to_pyerr { |
105 | ($err: ty, $pyexc: ty) => { |
106 | impl PyErrArguments for $err { |
107 | fn arguments(self, py: Python<'_>) -> PyObject { |
108 | // FIXME(icxolu) remove unwrap |
109 | self.to_string() |
110 | .into_pyobject(py) |
111 | .unwrap() |
112 | .into_any() |
113 | .unbind() |
114 | } |
115 | } |
116 | |
117 | impl std::convert::From<$err> for PyErr { |
118 | fn from(err: $err) -> PyErr { |
119 | <$pyexc>::new_err(err) |
120 | } |
121 | } |
122 | }; |
123 | } |
124 | |
125 | impl_to_pyerr!(std::array::TryFromSliceError, exceptions::PyValueError); |
126 | impl_to_pyerr!(std::num::ParseIntError, exceptions::PyValueError); |
127 | impl_to_pyerr!(std::num::ParseFloatError, exceptions::PyValueError); |
128 | impl_to_pyerr!(std::num::TryFromIntError, exceptions::PyValueError); |
129 | impl_to_pyerr!(std::str::ParseBoolError, exceptions::PyValueError); |
130 | impl_to_pyerr!(std::ffi::IntoStringError, exceptions::PyUnicodeDecodeError); |
131 | impl_to_pyerr!(std::ffi::NulError, exceptions::PyValueError); |
132 | impl_to_pyerr!(std::str::Utf8Error, exceptions::PyUnicodeDecodeError); |
133 | impl_to_pyerr!(std::string::FromUtf8Error, exceptions::PyUnicodeDecodeError); |
134 | impl_to_pyerr!( |
135 | std::string::FromUtf16Error, |
136 | exceptions::PyUnicodeDecodeError |
137 | ); |
138 | impl_to_pyerr!( |
139 | std::char::DecodeUtf16Error, |
140 | exceptions::PyUnicodeDecodeError |
141 | ); |
142 | impl_to_pyerr!(std::net::AddrParseError, exceptions::PyValueError); |
143 | |
144 | #[cfg (test)] |
145 | mod tests { |
146 | use crate::{PyErr, Python}; |
147 | use std::io; |
148 | |
149 | #[test ] |
150 | fn io_errors() { |
151 | use crate::types::any::PyAnyMethods; |
152 | |
153 | let check_err = |kind, expected_ty| { |
154 | Python::with_gil(|py| { |
155 | let rust_err = io::Error::new(kind, "some error msg" ); |
156 | |
157 | let py_err: PyErr = rust_err.into(); |
158 | let py_err_msg = format!("{}: some error msg" , expected_ty); |
159 | assert_eq!(py_err.to_string(), py_err_msg); |
160 | let py_error_clone = py_err.clone_ref(py); |
161 | |
162 | let rust_err_from_py_err: io::Error = py_err.into(); |
163 | assert_eq!(rust_err_from_py_err.to_string(), py_err_msg); |
164 | assert_eq!(rust_err_from_py_err.kind(), kind); |
165 | |
166 | let py_err_recovered_from_rust_err: PyErr = rust_err_from_py_err.into(); |
167 | assert!(py_err_recovered_from_rust_err |
168 | .value(py) |
169 | .is(py_error_clone.value(py))); // It should be the same exception |
170 | }) |
171 | }; |
172 | |
173 | check_err(io::ErrorKind::BrokenPipe, "BrokenPipeError" ); |
174 | check_err(io::ErrorKind::ConnectionRefused, "ConnectionRefusedError" ); |
175 | check_err(io::ErrorKind::ConnectionAborted, "ConnectionAbortedError" ); |
176 | check_err(io::ErrorKind::ConnectionReset, "ConnectionResetError" ); |
177 | check_err(io::ErrorKind::Interrupted, "InterruptedError" ); |
178 | check_err(io::ErrorKind::NotFound, "FileNotFoundError" ); |
179 | check_err(io::ErrorKind::PermissionDenied, "PermissionError" ); |
180 | check_err(io::ErrorKind::AlreadyExists, "FileExistsError" ); |
181 | check_err(io::ErrorKind::WouldBlock, "BlockingIOError" ); |
182 | check_err(io::ErrorKind::TimedOut, "TimeoutError" ); |
183 | #[cfg (io_error_more)] |
184 | check_err(io::ErrorKind::IsADirectory, "IsADirectoryError" ); |
185 | #[cfg (io_error_more)] |
186 | check_err(io::ErrorKind::NotADirectory, "NotADirectoryError" ); |
187 | } |
188 | } |
189 | |