1use std::borrow::Cow;
2
3use crate::{
4 exceptions,
5 types::{PyAnyMethods, PyStringMethods, PyTuple, PyTupleMethods, PyType, PyTypeMethods},
6 Borrowed, Bound, IntoPyObjectExt, Py, PyAny, PyErr, PyErrArguments, Python,
7};
8
9#[derive(Debug)]
11pub struct CastError<'a, 'py> {
12 from: Borrowed<'a, 'py, PyAny>,
14 to: Bound<'py, PyAny>,
17}
18
19impl<'a, 'py> CastError<'a, 'py> {
20 #[inline]
27 pub fn new(from: Borrowed<'a, 'py, PyAny>, to: Bound<'py, PyAny>) -> Self {
28 Self { from, to }
29 }
30}
31
32#[derive(Debug)]
34pub struct CastIntoError<'py> {
35 from: Bound<'py, PyAny>,
36 to: Bound<'py, PyAny>,
37}
38
39impl<'py> CastIntoError<'py> {
40 #[inline]
45 pub fn new(from: Bound<'py, PyAny>, to: Bound<'py, PyAny>) -> Self {
46 Self { from, to }
47 }
48
49 pub fn into_inner(self) -> Bound<'py, PyAny> {
54 self.from
55 }
56}
57
58struct CastErrorArguments {
59 from: Py<PyType>,
60 to: Py<PyAny>,
61}
62
63impl PyErrArguments for CastErrorArguments {
64 fn arguments(self, py: Python<'_>) -> Py<PyAny> {
65 format!(
66 "{}",
67 DisplayDowncastError {
68 from: &self.from.into_bound(py),
69 to: &self.to.into_bound(py),
70 }
71 )
72 .into_py_any(py)
73 .expect("failed to create Python string")
74 }
75}
76
77impl std::convert::From<CastError<'_, '_>> for PyErr {
79 fn from(err: CastError<'_, '_>) -> PyErr {
80 let args = CastErrorArguments {
81 from: err.from.get_type().unbind(),
82 to: err.to.unbind(),
83 };
84
85 exceptions::PyTypeError::new_err(args)
86 }
87}
88
89impl std::error::Error for CastError<'_, '_> {}
90
91impl std::fmt::Display for CastError<'_, '_> {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
93 DisplayDowncastError {
94 from: &self.from.get_type(),
95 to: &self.to,
96 }
97 .fmt(f)
98 }
99}
100
101impl std::convert::From<CastIntoError<'_>> for PyErr {
103 fn from(err: CastIntoError<'_>) -> PyErr {
104 let args = CastErrorArguments {
105 from: err.from.get_type().unbind(),
106 to: err.to.unbind(),
107 };
108
109 exceptions::PyTypeError::new_err(args)
110 }
111}
112
113impl std::error::Error for CastIntoError<'_> {}
114
115impl std::fmt::Display for CastIntoError<'_> {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
117 DisplayDowncastError {
118 from: &self.from.get_type(),
119 to: &self.to,
120 }
121 .fmt(f)
122 }
123}
124
125struct DisplayDowncastError<'a, 'py> {
126 from: &'a Bound<'py, PyType>,
127 to: &'a Bound<'py, PyAny>,
128}
129
130impl std::fmt::Display for DisplayDowncastError<'_, '_> {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 let from = self.from.qualname();
133 let from = from
134 .as_ref()
135 .map(|name| name.to_string_lossy())
136 .unwrap_or(Cow::Borrowed("<failed to extract type name>"));
137 let to = DisplayClassInfo(self.to);
138 write!(f, "'{from}' object cannot be cast as '{to}'")
139 }
140}
141
142struct DisplayClassInfo<'a, 'py>(&'a Bound<'py, PyAny>);
143
144impl std::fmt::Display for DisplayClassInfo<'_, '_> {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 if let Ok(t) = self.0.cast::<PyType>() {
147 t.qualname()
148 .map_err(|_| std::fmt::Error)?
149 .to_string_lossy()
150 .fmt(f)
151 } else if let Ok(t) = self.0.cast::<PyTuple>() {
152 for (i, t) in t.iter().enumerate() {
153 if i > 0 {
154 f.write_str(" | ")?;
155 }
156 write!(f, "{}", DisplayClassInfo(&t))?;
157 }
158 Ok(())
159 } else {
160 self.0.fmt(f)
161 }
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use crate::PyTypeInfo;
168
169 use super::*;
170
171 #[test]
172 fn test_display_cast_error() {
173 Python::attach(|py| {
174 let obj = py.None().into_bound(py);
175 let to_type = py.get_type::<crate::types::PyInt>().into_any();
176 let err = CastError::new(obj.as_borrowed(), to_type);
177 assert_eq!(err.to_string(), "'NoneType' object cannot be cast as 'int'");
178 })
179 }
180
181 #[test]
182 fn test_display_cast_error_with_tuple() {
183 Python::attach(|py| {
184 let obj = py.None().into_bound(py);
185 let to_type = PyTuple::new(
186 py,
187 &[
188 py.get_type::<crate::types::PyInt>().into_any(),
189 crate::types::PyNone::type_object(py).into_any(),
190 ],
191 )
192 .unwrap()
193 .into_any();
194 let err = CastError::new(obj.as_borrowed(), to_type);
195 assert_eq!(
196 err.to_string(),
197 "'NoneType' object cannot be cast as 'int | NoneType'"
198 );
199 })
200 }
201
202 #[test]
203 fn test_display_cast_into_error() {
204 Python::attach(|py| {
205 let obj = py.None().into_bound(py);
206 let to_type = py.get_type::<crate::types::PyInt>().into_any();
207 let err = CastIntoError::new(obj, to_type);
208 assert_eq!(err.to_string(), "'NoneType' object cannot be cast as 'int'");
209 })
210 }
211
212 #[test]
213 fn test_pyerr_from_cast_error() {
214 Python::attach(|py| {
215 let obj = py.None().into_bound(py);
216 let to_type = py.get_type::<crate::types::PyInt>().into_any();
217 let err = CastError::new(obj.as_borrowed(), to_type);
218 let py_err: PyErr = err.into();
219 assert_eq!(
220 py_err.to_string(),
221 "TypeError: 'NoneType' object cannot be cast as 'int'"
222 );
223 })
224 }
225
226 #[test]
227 fn test_pyerr_from_cast_into_error() {
228 Python::attach(|py| {
229 let obj = py.None().into_bound(py);
230 let to_type = py.get_type::<crate::types::PyInt>().into_any();
231 let err = CastIntoError::new(obj, to_type);
232 let py_err: PyErr = err.into();
233 assert_eq!(
234 py_err.to_string(),
235 "TypeError: 'NoneType' object cannot be cast as 'int'"
236 );
237 })
238 }
239}