plist/
error.rs

1use std::{error, fmt, io};
2use quick_xml::escape::EscapeError;
3use quick_xml::encoding::EncodingError;
4
5#[cfg(feature = "serde")]
6use crate::stream::Event;
7use crate::{InvalidXmlDate, Value};
8
9/// This type represents all possible errors that can occur when working with plist data.
10#[derive(Debug)]
11pub struct Error {
12    inner: Box<ErrorImpl>,
13}
14
15#[derive(Debug)]
16pub(crate) struct ErrorImpl {
17    kind: ErrorKind,
18    file_position: Option<FilePosition>,
19}
20
21#[derive(Debug)]
22pub(crate) enum ErrorKind {
23    UnexpectedEof,
24    UnexpectedEndOfEventStream,
25    UnexpectedEventType {
26        // Used by the `Debug` implementation.
27        #[allow(dead_code)]
28        expected: EventKind,
29        #[allow(dead_code)]
30        found: EventKind,
31    },
32    ExpectedEndOfEventStream {
33        // Used by the `Debug` implementation.
34        #[allow(dead_code)]
35        found: EventKind,
36    },
37
38    // Ascii format-specific errors
39    UnclosedString,
40    IncompleteComment,
41    InvalidUtf8AsciiStream,
42    InvalidOctalString,
43
44    // Xml format-specific errors
45    UnclosedXmlElement,
46    UnexpectedXmlCharactersExpectedElement,
47    UnexpectedXmlOpeningTag,
48    UnknownXmlElement,
49    InvalidXmlSyntax,
50    InvalidXmlUtf8,
51    InvalidDataString,
52    InvalidDateString,
53    InvalidIntegerString,
54    InvalidRealString,
55    UidNotSupportedInXmlPlist,
56
57    // Binary format-specific errors
58    ObjectTooLarge,
59    InvalidMagic,
60    InvalidTrailerObjectOffsetSize, // the size of byte offsets to objects in the object table
61    InvalidTrailerObjectReferenceSize, // the size of indices into the object table
62    InvalidObjectLength,
63    ObjectReferenceTooLarge,
64    ObjectOffsetTooLarge,
65    RecursiveObject,
66    NullObjectUnimplemented,
67    FillObjectUnimplemented,
68    IntegerOutOfRange,
69    InfiniteOrNanDate,
70    InvalidUtf8String,
71    InvalidUtf16String,
72    UnknownObjectType(
73        // Used by the `Debug` implementation.
74        #[allow(dead_code)] u8,
75    ),
76
77    Io(io::Error),
78    #[cfg(feature = "serde")]
79    Serde(
80        // Used by the `Debug` implementation.
81        #[allow(dead_code)] String,
82    ),
83}
84
85#[derive(Debug, Clone, Copy)]
86pub(crate) struct FilePosition(pub(crate) u64);
87
88#[derive(Copy, Clone, PartialEq, Eq, Debug)]
89pub(crate) enum EventKind {
90    StartArray,
91    StartDictionary,
92    EndCollection,
93    Boolean,
94    Data,
95    Date,
96    Integer,
97    Real,
98    String,
99    Uid,
100
101    ValueOrStartCollection,
102    DictionaryKeyOrEndCollection,
103}
104
105impl Error {
106    /// Returns true if this error was caused by a failure to read or write bytes on an IO stream.
107    pub fn is_io(&self) -> bool {
108        self.as_io().is_some()
109    }
110
111    /// Returns true if this error was caused by prematurely reaching the end of the input data.
112    pub fn is_eof(&self) -> bool {
113        matches!(self.inner.kind, ErrorKind::UnexpectedEof)
114    }
115
116    /// Returns the underlying error if it was caused by a failure to read or write bytes on an IO
117    /// stream.
118    pub fn as_io(&self) -> Option<&io::Error> {
119        if let ErrorKind::Io(err) = &self.inner.kind {
120            Some(err)
121        } else {
122            None
123        }
124    }
125
126    /// Returns the underlying error if it was caused by a failure to read or write bytes on an IO
127    /// stream or `self` if it was not.
128    pub fn into_io(self) -> Result<io::Error, Self> {
129        if let ErrorKind::Io(err) = self.inner.kind {
130            Ok(err)
131        } else {
132            Err(self)
133        }
134    }
135}
136
137impl error::Error for Error {
138    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
139        match &self.inner.kind {
140            ErrorKind::Io(err) => Some(err),
141            _ => None,
142        }
143    }
144}
145
146impl fmt::Display for Error {
147    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148        if let Some(position) = &self.inner.file_position {
149            write!(f, "{:?} ({})", &self.inner.kind, position)
150        } else {
151            fmt::Debug::fmt(&self.inner.kind, f)
152        }
153    }
154}
155
156impl fmt::Display for FilePosition {
157    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158        write!(f, "offset {}", self.0)
159    }
160}
161
162impl From<InvalidXmlDate> for Error {
163    fn from(error: InvalidXmlDate) -> Self {
164        ErrorKind::from(error).without_position()
165    }
166}
167
168impl ErrorKind {
169    pub fn with_byte_offset(self, offset: u64) -> Error {
170        self.with_position(FilePosition(offset))
171    }
172
173    pub fn with_position(self, pos: FilePosition) -> Error {
174        Error {
175            inner: Box::new(ErrorImpl {
176                kind: self,
177                file_position: Some(pos),
178            }),
179        }
180    }
181
182    pub fn without_position(self) -> Error {
183        Error {
184            inner: Box::new(ErrorImpl {
185                kind: self,
186                file_position: None,
187            }),
188        }
189    }
190}
191
192impl From<InvalidXmlDate> for ErrorKind {
193    fn from(_: InvalidXmlDate) -> Self {
194        ErrorKind::InvalidDateString
195    }
196}
197
198impl From<EscapeError> for ErrorKind {
199    fn from(_: EscapeError) -> Self {
200        ErrorKind::InvalidXmlUtf8
201    }
202}
203
204impl From<EncodingError> for ErrorKind {
205    fn from(_: EncodingError) -> Self {
206        ErrorKind::InvalidXmlUtf8
207    }
208}
209
210impl EventKind {
211    #[cfg(feature = "serde")]
212    pub fn of_event(event: &Event) -> EventKind {
213        match event {
214            Event::StartArray(_) => EventKind::StartArray,
215            Event::StartDictionary(_) => EventKind::StartDictionary,
216            Event::EndCollection => EventKind::EndCollection,
217            Event::Boolean(_) => EventKind::Boolean,
218            Event::Data(_) => EventKind::Data,
219            Event::Date(_) => EventKind::Date,
220            Event::Integer(_) => EventKind::Integer,
221            Event::Real(_) => EventKind::Real,
222            Event::String(_) => EventKind::String,
223            Event::Uid(_) => EventKind::Uid,
224        }
225    }
226
227    pub fn of_value(event: &Value) -> EventKind {
228        match event {
229            Value::Array(_) => EventKind::StartArray,
230            Value::Dictionary(_) => EventKind::StartDictionary,
231            Value::Boolean(_) => EventKind::Boolean,
232            Value::Data(_) => EventKind::Data,
233            Value::Date(_) => EventKind::Date,
234            Value::Integer(_) => EventKind::Integer,
235            Value::Real(_) => EventKind::Real,
236            Value::String(_) => EventKind::String,
237            Value::Uid(_) => EventKind::Uid,
238        }
239    }
240}
241
242impl fmt::Display for EventKind {
243    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244        match self {
245            EventKind::StartArray => "StartArray",
246            EventKind::StartDictionary => "StartDictionary",
247            EventKind::EndCollection => "EndCollection",
248            EventKind::Boolean => "Boolean",
249            EventKind::Data => "Data",
250            EventKind::Date => "Date",
251            EventKind::Integer => "Integer",
252            EventKind::Real => "Real",
253            EventKind::String => "String",
254            EventKind::Uid => "Uid",
255            EventKind::ValueOrStartCollection => "value or start collection",
256            EventKind::DictionaryKeyOrEndCollection => "dictionary key or end collection",
257        }
258        .fmt(f)
259    }
260}
261
262pub(crate) fn from_io_without_position(err: io::Error) -> Error {
263    ErrorKind::Io(err).without_position()
264}
265
266#[cfg(feature = "serde")]
267pub(crate) fn unexpected_event_type(expected: EventKind, found: &Event) -> Error {
268    let found = EventKind::of_event(found);
269    ErrorKind::UnexpectedEventType { expected, found }.without_position()
270}