| 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 | |