pythonize/
error.rs

1use pyo3::PyErr;
2use pyo3::{exceptions::*, DowncastError, DowncastIntoError};
3use serde::{de, ser};
4use std::convert::Infallible;
5use std::error;
6use std::fmt::{self, Debug, Display};
7use std::result;
8
9/// Alias for `std::result::Result` with error type `PythonizeError`
10pub type Result<T> = result::Result<T, PythonizeError>;
11
12/// Errors that can occur when serializing/deserializing Python objects
13pub struct PythonizeError {
14    pub(crate) inner: Box<ErrorImpl>,
15}
16
17impl PythonizeError {
18    pub(crate) fn msg<T>(text: T) -> Self
19    where
20        T: ToString,
21    {
22        Self {
23            inner: Box::new(ErrorImpl::Message(text.to_string())),
24        }
25    }
26
27    pub(crate) fn unsupported_type<T>(t: T) -> Self
28    where
29        T: ToString,
30    {
31        Self {
32            inner: Box::new(ErrorImpl::UnsupportedType(t.to_string())),
33        }
34    }
35
36    pub(crate) fn dict_key_not_string() -> Self {
37        Self {
38            inner: Box::new(ErrorImpl::DictKeyNotString),
39        }
40    }
41
42    pub(crate) fn incorrect_sequence_length(expected: usize, got: usize) -> Self {
43        Self {
44            inner: Box::new(ErrorImpl::IncorrectSequenceLength { expected, got }),
45        }
46    }
47
48    pub(crate) fn invalid_enum_type() -> Self {
49        Self {
50            inner: Box::new(ErrorImpl::InvalidEnumType),
51        }
52    }
53
54    pub(crate) fn invalid_length_enum() -> Self {
55        Self {
56            inner: Box::new(ErrorImpl::InvalidLengthEnum),
57        }
58    }
59
60    pub(crate) fn invalid_length_char() -> Self {
61        Self {
62            inner: Box::new(ErrorImpl::InvalidLengthChar),
63        }
64    }
65}
66
67/// Error codes for problems that can occur when serializing/deserializing Python objects
68#[derive(Debug)]
69pub enum ErrorImpl {
70    /// An error originating from the Python runtime
71    PyErr(PyErr),
72    /// Generic error message
73    Message(String),
74    /// A Python type not supported by the deserializer
75    UnsupportedType(String),
76    /// A `PyAny` object that failed to downcast to an expected Python type
77    UnexpectedType(String),
78    /// Dict keys should be strings to deserialize to struct fields
79    DictKeyNotString,
80    /// Sequence length did not match expected tuple or tuple struct length.
81    IncorrectSequenceLength { expected: usize, got: usize },
82    /// Enum variants should either be dict (tagged) or str (variant)
83    InvalidEnumType,
84    /// Tagged enum variants should be a dict with exactly 1 key
85    InvalidLengthEnum,
86    /// Expected a `char`, but got a Python str that was not length 1
87    InvalidLengthChar,
88}
89
90impl error::Error for PythonizeError {}
91
92impl Display for PythonizeError {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        match self.inner.as_ref() {
95            ErrorImpl::PyErr(e) => Display::fmt(e, f),
96            ErrorImpl::Message(s) => Display::fmt(s, f),
97            ErrorImpl::UnsupportedType(s) => write!(f, "unsupported type {}", s),
98            ErrorImpl::UnexpectedType(s) => write!(f, "unexpected type: {}", s),
99            ErrorImpl::DictKeyNotString => f.write_str("dict keys must have type str"),
100            ErrorImpl::IncorrectSequenceLength { expected, got } => {
101                write!(f, "expected sequence of length {}, got {}", expected, got)
102            }
103            ErrorImpl::InvalidEnumType => f.write_str("expected either a str or dict for enum"),
104            ErrorImpl::InvalidLengthEnum => {
105                f.write_str("expected tagged enum dict to have exactly 1 key")
106            }
107            ErrorImpl::InvalidLengthChar => f.write_str("expected a str of length 1 for char"),
108        }
109    }
110}
111
112impl Debug for PythonizeError {
113    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
114        self.inner.as_ref().fmt(f)
115    }
116}
117
118impl ser::Error for PythonizeError {
119    fn custom<T>(msg: T) -> Self
120    where
121        T: Display,
122    {
123        Self {
124            inner: Box::new(ErrorImpl::Message(msg.to_string())),
125        }
126    }
127}
128
129impl de::Error for PythonizeError {
130    fn custom<T>(msg: T) -> Self
131    where
132        T: Display,
133    {
134        Self {
135            inner: Box::new(ErrorImpl::Message(msg.to_string())),
136        }
137    }
138}
139
140/// Convert an exception raised in Python to a `PythonizeError`
141impl From<Infallible> for PythonizeError {
142    fn from(other: Infallible) -> Self {
143        match other {}
144    }
145}
146
147/// Convert an exception raised in Python to a `PythonizeError`
148impl From<PyErr> for PythonizeError {
149    fn from(other: PyErr) -> Self {
150        Self {
151            inner: Box::new(ErrorImpl::PyErr(other)),
152        }
153    }
154}
155
156/// Handle errors that occur when attempting to use `PyAny::cast_as`
157impl<'a, 'py> From<DowncastError<'a, 'py>> for PythonizeError {
158    fn from(other: DowncastError<'a, 'py>) -> Self {
159        Self {
160            inner: Box::new(ErrorImpl::UnexpectedType(other.to_string())),
161        }
162    }
163}
164
165/// Handle errors that occur when attempting to use `PyAny::cast_as`
166impl<'py> From<DowncastIntoError<'py>> for PythonizeError {
167    fn from(other: DowncastIntoError<'py>) -> Self {
168        Self {
169            inner: Box::new(ErrorImpl::UnexpectedType(other.to_string())),
170        }
171    }
172}
173
174/// Convert a `PythonizeError` to a Python exception
175impl From<PythonizeError> for PyErr {
176    fn from(other: PythonizeError) -> Self {
177        match *other.inner {
178            ErrorImpl::PyErr(e) => e,
179            ErrorImpl::Message(e) => PyException::new_err(e),
180            ErrorImpl::UnsupportedType(_)
181            | ErrorImpl::UnexpectedType(_)
182            | ErrorImpl::DictKeyNotString
183            | ErrorImpl::InvalidEnumType => PyTypeError::new_err(other.to_string()),
184            ErrorImpl::IncorrectSequenceLength { .. }
185            | ErrorImpl::InvalidLengthEnum
186            | ErrorImpl::InvalidLengthChar => PyValueError::new_err(other.to_string()),
187        }
188    }
189}