1use crate::format::Format;
4
5pub type Result<T, E = Error> = std::result::Result<T, E>;
9
10type Source = Box<dyn std::error::Error + Send + Sync>;
12
13#[derive(Debug, thiserror::Error)]
18#[non_exhaustive]
19pub enum Error {
20 #[error("i/o error")]
22 Io(#[from] std::io::Error),
23
24 #[error("invalid {format} property list")]
29 #[non_exhaustive]
30 InvalidPlist {
31 format: &'static str,
33 source: Option<Source>,
35 },
36
37 #[error("error parsing {format} property list")]
40 #[non_exhaustive]
41 Parse {
42 format: &'static str,
44 source: Option<Source>,
46 },
47
48 #[error("can't marshal value of type {0}")]
50 UnknownType(&'static str),
51
52 #[error("cannot decode {found} into {expected}")]
54 #[non_exhaustive]
55 TypeMismatch {
56 expected: &'static str,
58 found: &'static str,
60 },
61
62 #[error("maximum nesting depth exceeded")]
65 MaxDepthExceeded,
66
67 #[error("{0}")]
69 ParseScalar(String),
70
71 #[error("no root element to encode")]
73 NoRootElement,
74
75 #[error("null is not representable")]
77 NullNotRepresentable,
78
79 #[error("{0}")]
81 Message(String),
82
83 #[error("support for the {format} format is disabled")]
86 #[non_exhaustive]
87 FeatureDisabled {
88 format: Format,
90 },
91}
92
93impl Error {
94 #[cfg(any(test, not(feature = "binary"), not(feature = "xml")))]
97 pub(crate) fn invalid(format: &'static str) -> Self {
98 Self::InvalidPlist {
99 format,
100 source: None,
101 }
102 }
103
104 #[cfg(any(test, feature = "binary", feature = "xml", feature = "openstep"))]
105 pub(crate) fn parse(format: &'static str, source: impl Into<Source>) -> Self {
106 Self::Parse {
107 format,
108 source: Some(source.into()),
109 }
110 }
111
112 pub(crate) const fn is_retry_signal(&self) -> bool {
115 matches!(self, Self::InvalidPlist { .. })
116 }
117}
118
119#[cfg(feature = "serde")]
120impl serde::ser::Error for Error {
121 fn custom<T: std::fmt::Display>(msg: T) -> Self {
122 Self::Message(msg.to_string())
123 }
124}
125
126#[cfg(feature = "serde")]
127impl serde::de::Error for Error {
128 fn custom<T: std::fmt::Display>(msg: T) -> Self {
129 Self::Message(msg.to_string())
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 const fn assert_send_sync<T: Send + Sync + 'static>() {}
138 const _: () = assert_send_sync::<Error>();
139
140 #[test]
141 fn display_messages_are_lowercase_unprefixed_unterminated() {
142 let cases = [
143 (Error::invalid("XML"), "invalid XML property list"),
144 (
145 Error::parse("binary", "bad trailer"),
146 "error parsing binary property list",
147 ),
148 (
149 Error::UnknownType("chan"),
150 "can't marshal value of type chan",
151 ),
152 (
153 Error::TypeMismatch {
154 expected: "u64",
155 found: "string",
156 },
157 "cannot decode string into u64",
158 ),
159 (Error::MaxDepthExceeded, "maximum nesting depth exceeded"),
160 (
161 Error::ParseScalar("invalid digit found in string".to_owned()),
162 "invalid digit found in string",
163 ),
164 (Error::NoRootElement, "no root element to encode"),
165 (Error::NullNotRepresentable, "null is not representable"),
166 (Error::Message("boom".to_owned()), "boom"),
167 (
168 Error::FeatureDisabled {
169 format: Format::Binary,
170 },
171 "support for the Binary format is disabled",
172 ),
173 ];
174 for (err, want) in cases {
175 assert_eq!(err.to_string(), want);
176 }
177 }
178
179 #[test]
180 fn parse_carries_its_source() {
181 let err = Error::parse("text", "unterminated string");
182 let source = std::error::Error::source(&err).map(ToString::to_string);
183 assert_eq!(source.as_deref(), Some("unterminated string"));
184 }
185
186 #[test]
187 fn invalid_has_no_source() {
188 let err = Error::invalid("XML");
189 assert!(std::error::Error::source(&err).is_none());
190 }
191
192 #[test]
193 fn retry_signal_is_invalid_plist_only() {
194 assert!(Error::invalid("XML").is_retry_signal());
195 assert!(!Error::parse("XML", "boom").is_retry_signal());
196 assert!(!Error::MaxDepthExceeded.is_retry_signal());
197 assert!(!Error::Io(std::io::Error::other("io")).is_retry_signal());
198 }
199
200 #[cfg(feature = "serde")]
201 #[test]
202 fn serde_custom_maps_to_message() {
203 let ser = <Error as serde::ser::Error>::custom("ser oops");
204 let de = <Error as serde::de::Error>::custom("de oops");
205 assert!(matches!(ser, Error::Message(ref m) if m == "ser oops"));
206 assert!(matches!(de, Error::Message(ref m) if m == "de oops"));
207 }
208}