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