use crate::format::Format;
pub type Result<T, E = Error> = std::result::Result<T, E>;
type Source = Box<dyn std::error::Error + Send + Sync>;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("i/o error")]
Io(#[from] std::io::Error),
#[error("invalid {format} property list")]
#[non_exhaustive]
InvalidPlist {
format: &'static str,
source: Option<Source>,
},
#[error("error parsing {format} property list")]
#[non_exhaustive]
Parse {
format: &'static str,
source: Option<Source>,
},
#[error("can't marshal value of type {0}")]
UnknownType(&'static str),
#[error("cannot decode {found} into {expected}")]
#[non_exhaustive]
TypeMismatch {
expected: &'static str,
found: &'static str,
},
#[error("maximum nesting depth exceeded")]
MaxDepthExceeded,
#[error("{0}")]
ParseScalar(String),
#[error("no root element to encode")]
NoRootElement,
#[error("null is not representable")]
NullNotRepresentable,
#[error("{0}")]
Message(String),
#[error("support for the {format} format is disabled")]
#[non_exhaustive]
FeatureDisabled {
format: Format,
},
}
impl Error {
#[cfg(any(test, not(feature = "binary"), not(feature = "xml")))]
pub(crate) fn invalid(format: &'static str) -> Self {
Self::InvalidPlist {
format,
source: None,
}
}
#[cfg(any(test, feature = "binary", feature = "xml", feature = "openstep"))]
pub(crate) fn parse(format: &'static str, source: impl Into<Source>) -> Self {
Self::Parse {
format,
source: Some(source.into()),
}
}
pub(crate) const fn is_retry_signal(&self) -> bool {
matches!(self, Self::InvalidPlist { .. })
}
}
#[cfg(feature = "serde")]
impl serde::ser::Error for Error {
fn custom<T: std::fmt::Display>(msg: T) -> Self {
Self::Message(msg.to_string())
}
}
#[cfg(feature = "serde")]
impl serde::de::Error for Error {
fn custom<T: std::fmt::Display>(msg: T) -> Self {
Self::Message(msg.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
const fn assert_send_sync<T: Send + Sync + 'static>() {}
const _: () = assert_send_sync::<Error>();
#[test]
fn display_messages_are_lowercase_unprefixed_unterminated() {
let cases = [
(Error::invalid("XML"), "invalid XML property list"),
(
Error::parse("binary", "bad trailer"),
"error parsing binary property list",
),
(
Error::UnknownType("chan"),
"can't marshal value of type chan",
),
(
Error::TypeMismatch {
expected: "u64",
found: "string",
},
"cannot decode string into u64",
),
(Error::MaxDepthExceeded, "maximum nesting depth exceeded"),
(
Error::ParseScalar("invalid digit found in string".to_owned()),
"invalid digit found in string",
),
(Error::NoRootElement, "no root element to encode"),
(Error::NullNotRepresentable, "null is not representable"),
(Error::Message("boom".to_owned()), "boom"),
(
Error::FeatureDisabled {
format: Format::Binary,
},
"support for the Binary format is disabled",
),
];
for (err, want) in cases {
assert_eq!(err.to_string(), want);
}
}
#[test]
fn parse_carries_its_source() {
let err = Error::parse("text", "unterminated string");
let source = std::error::Error::source(&err).map(ToString::to_string);
assert_eq!(source.as_deref(), Some("unterminated string"));
}
#[test]
fn invalid_has_no_source() {
let err = Error::invalid("XML");
assert!(std::error::Error::source(&err).is_none());
}
#[test]
fn retry_signal_is_invalid_plist_only() {
assert!(Error::invalid("XML").is_retry_signal());
assert!(!Error::parse("XML", "boom").is_retry_signal());
assert!(!Error::MaxDepthExceeded.is_retry_signal());
assert!(!Error::Io(std::io::Error::other("io")).is_retry_signal());
}
#[cfg(feature = "serde")]
#[test]
fn serde_custom_maps_to_message() {
let ser = <Error as serde::ser::Error>::custom("ser oops");
let de = <Error as serde::de::Error>::custom("de oops");
assert!(matches!(ser, Error::Message(ref m) if m == "ser oops"));
assert!(matches!(de, Error::Message(ref m) if m == "de oops"));
}
}