1use std::borrow::Cow;
2
3use crate::{
4 exceptions,
5 types::{
6 PyAnyMethods, PyNone, PyStringMethods, PyTuple, PyTupleMethods, PyType, PyTypeMethods,
7 },
8 Borrowed, Bound, IntoPyObjectExt, Py, PyAny, PyErr, PyErrArguments, PyTypeInfo, Python,
9};
10
11#[derive(Debug)]
13pub struct CastError<'a, 'py> {
14 from: Borrowed<'a, 'py, PyAny>,
16 classinfo: Bound<'py, PyAny>,
19}
20
21impl<'a, 'py> CastError<'a, 'py> {
22 #[inline]
29 pub fn new(from: Borrowed<'a, 'py, PyAny>, classinfo: Bound<'py, PyAny>) -> Self {
30 Self { from, classinfo }
31 }
32}
33
34#[derive(Debug)]
39pub struct CastIntoError<'py> {
40 from: Bound<'py, PyAny>,
41 classinfo: Bound<'py, PyAny>,
42}
43
44impl<'py> CastIntoError<'py> {
45 #[inline]
50 pub fn new(from: Bound<'py, PyAny>, classinfo: Bound<'py, PyAny>) -> Self {
51 Self { from, classinfo }
52 }
53
54 pub fn into_inner(self) -> Bound<'py, PyAny> {
59 self.from
60 }
61}
62
63struct CastErrorArguments {
64 from: Py<PyAny>,
65 classinfo: Py<PyAny>,
66}
67
68impl PyErrArguments for CastErrorArguments {
69 fn arguments(self, py: Python<'_>) -> Py<PyAny> {
70 format!(
71 "{}",
72 DisplayCastError {
73 from: &self.from.into_bound(py),
74 classinfo: &self.classinfo.into_bound(py),
75 }
76 )
77 .into_py_any(py)
78 .expect("failed to create Python string")
79 }
80}
81
82impl std::convert::From<CastError<'_, '_>> for PyErr {
84 fn from(err: CastError<'_, '_>) -> PyErr {
85 let args = CastErrorArguments {
86 from: err.from.to_owned().unbind(),
87 classinfo: err.classinfo.unbind(),
88 };
89
90 exceptions::PyTypeError::new_err(args)
91 }
92}
93
94impl std::error::Error for CastError<'_, '_> {}
95
96impl std::fmt::Display for CastError<'_, '_> {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
98 DisplayCastError {
99 from: &self.from,
100 classinfo: &self.classinfo,
101 }
102 .fmt(f)
103 }
104}
105
106impl std::convert::From<CastIntoError<'_>> for PyErr {
108 fn from(err: CastIntoError<'_>) -> PyErr {
109 let args = CastErrorArguments {
110 from: err.from.to_owned().unbind(),
111 classinfo: err.classinfo.unbind(),
112 };
113
114 exceptions::PyTypeError::new_err(args)
115 }
116}
117
118impl std::error::Error for CastIntoError<'_> {}
119
120impl std::fmt::Display for CastIntoError<'_> {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
122 DisplayCastError {
123 from: &self.from.to_owned(),
124 classinfo: &self.classinfo,
125 }
126 .fmt(f)
127 }
128}
129
130struct DisplayCastError<'a, 'py> {
131 from: &'a Bound<'py, PyAny>,
132 classinfo: &'a Bound<'py, PyAny>,
133}
134
135impl std::fmt::Display for DisplayCastError<'_, '_> {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 let to = DisplayClassInfo(self.classinfo);
138 if self.from.is_none() {
139 write!(f, "'None' is not an instance of '{to}'")
140 } else {
141 let from = self.from.get_type().qualname();
142 let from = from
143 .as_ref()
144 .map(|name| name.to_string_lossy())
145 .unwrap_or(Cow::Borrowed("<failed to extract type name>"));
146 write!(f, "'{from}' object is not an instance of '{to}'")
147 }
148 }
149}
150
151struct DisplayClassInfo<'a, 'py>(&'a Bound<'py, PyAny>);
152
153impl std::fmt::Display for DisplayClassInfo<'_, '_> {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 if let Ok(t) = self.0.cast::<PyType>() {
156 if t.is(PyNone::type_object(t.py())) {
157 f.write_str("None")
158 } else {
159 t.qualname()
160 .map_err(|_| std::fmt::Error)?
161 .to_string_lossy()
162 .fmt(f)
163 }
164 } else if let Ok(t) = self.0.cast::<PyTuple>() {
165 for (i, t) in t.iter().enumerate() {
166 if i > 0 {
167 f.write_str(" | ")?;
168 }
169 write!(f, "{}", DisplayClassInfo(&t))?;
170 }
171 Ok(())
172 } else {
173 self.0.fmt(f)
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use crate::{
181 types::{PyBool, PyString},
182 PyTypeInfo,
183 };
184
185 use super::*;
186
187 #[test]
188 fn test_display_cast_error() {
189 Python::attach(|py| {
190 let obj = PyBool::new(py, true).to_any();
191 let classinfo = py.get_type::<PyString>().into_any();
192 let err = CastError::new(obj, classinfo);
193 assert_eq!(err.to_string(), "'bool' object is not an instance of 'str'");
194 })
195 }
196
197 #[test]
198 fn test_display_cast_error_with_none() {
199 Python::attach(|py| {
200 let obj = py.None().into_bound(py);
201 let classinfo = py.get_type::<PyString>().into_any();
202 let err = CastError::new(obj.as_borrowed(), classinfo);
203 assert_eq!(err.to_string(), "'None' is not an instance of 'str'");
204 })
205 }
206
207 #[test]
208 fn test_display_cast_error_with_tuple() {
209 Python::attach(|py| {
210 let obj = PyBool::new(py, true).to_any();
211 let classinfo = PyTuple::new(
212 py,
213 &[
214 py.get_type::<PyString>().into_any(),
215 crate::types::PyNone::type_object(py).into_any(),
216 ],
217 )
218 .unwrap()
219 .into_any();
220 let err = CastError::new(obj, classinfo);
221 assert_eq!(
222 err.to_string(),
223 "'bool' object is not an instance of 'str | None'"
224 );
225 })
226 }
227
228 #[test]
229 fn test_display_cast_into_error() {
230 Python::attach(|py| {
231 let obj = PyBool::new(py, true).to_any();
232 let classinfo = py.get_type::<PyString>().into_any();
233 let err = CastIntoError::new(obj.to_owned(), classinfo);
234 assert_eq!(err.to_string(), "'bool' object is not an instance of 'str'");
235 })
236 }
237
238 #[test]
239 fn test_pyerr_from_cast_error() {
240 Python::attach(|py| {
241 let obj = PyBool::new(py, true).to_any();
242 let classinfo = py.get_type::<PyString>().into_any();
243 let err = CastError::new(obj, classinfo);
244 let py_err: PyErr = err.into();
245 assert_eq!(
246 py_err.to_string(),
247 "TypeError: 'bool' object is not an instance of 'str'"
248 );
249 })
250 }
251
252 #[test]
253 fn test_pyerr_from_cast_into_error() {
254 Python::attach(|py| {
255 let obj = PyBool::new(py, true).to_any();
256 let classinfo = py.get_type::<PyString>().into_any();
257 let err = CastIntoError::new(obj.to_owned(), classinfo);
258 let py_err: PyErr = err.into();
259 assert_eq!(
260 py_err.to_string(),
261 "TypeError: 'bool' object is not an instance of 'str'"
262 );
263 })
264 }
265}