use std::{convert::Infallible, error, fmt, io, sync::Arc};
use serde::{de, ser};
#[cfg(feature = "comms")]
use crate::{
Address, fdo,
message::{Message, Type},
names::OwnedErrorName,
};
use crate::{ObjectPath, Signature, names::InterfaceName, wire};
#[derive(Clone, Debug)]
#[non_exhaustive]
#[allow(clippy::upper_case_acronyms)]
pub enum Error {
Failure(String),
InputOutput(Arc<io::Error>),
IncorrectType,
Utf8(std::str::Utf8Error),
PaddingNot0(u8),
UnknownFd,
SignatureMismatch(Signature, String),
OutOfBounds,
MaxDepthExceeded(MaxDepthExceeded),
SignatureParse(wire::signature::Error),
EmptyStructure,
InvalidObjectPath,
InvalidName(&'static str),
InvalidNameConversion {
from: &'static str,
to: &'static str,
},
InterfaceNotFound,
Address(String),
InvalidField,
ExcessData,
IncorrectEndian,
Handshake(String),
InvalidReply,
#[cfg(feature = "comms")]
MethodError(OwnedErrorName, Option<String>, Message),
MissingField,
InvalidGUID,
Unsupported,
#[cfg(feature = "comms")]
FDO(Box<fdo::Error>),
#[cfg(feature = "comms")]
DBus(Arc<dyn crate::DBusError + Send + Sync>),
NameTaken,
InvalidMatchRule,
MissingParameter(&'static str),
InvalidSerial,
InterfaceExists(InterfaceName<'static>, ObjectPath<'static>),
#[cfg(feature = "comms")]
Connection(
Arc<io::Error>,
Box<Address>,
),
}
const _: () = assert!(size_of::<Error>() <= 64);
impl PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Failure(s1), Self::Failure(s2)) => s1 == s2,
(Self::IncorrectType, Self::IncorrectType) => true,
(Self::Utf8(s), Self::Utf8(o)) => s == o,
(Self::PaddingNot0(s), Self::PaddingNot0(o)) => s == o,
(Self::UnknownFd, Self::UnknownFd) => true,
(Self::SignatureMismatch(p1, e1), Self::SignatureMismatch(p2, e2)) => {
p1 == p2 && e1 == e2
}
(Self::OutOfBounds, Self::OutOfBounds) => true,
(Self::MaxDepthExceeded(m1), Self::MaxDepthExceeded(m2)) => m1 == m2,
(Self::SignatureParse(e1), Self::SignatureParse(e2)) => e1 == e2,
(Self::EmptyStructure, Self::EmptyStructure) => true,
(Self::InvalidObjectPath, Self::InvalidObjectPath) => true,
(Self::InvalidName(_), Self::InvalidName(_)) => true,
(Self::InvalidNameConversion { .. }, Self::InvalidNameConversion { .. }) => true,
(Self::InterfaceNotFound, Self::InterfaceNotFound) => true,
(Self::Address(_), Self::Address(_)) => true,
(Self::InvalidField, Self::InvalidField) => true,
(Self::ExcessData, Self::ExcessData) => true,
(Self::IncorrectEndian, Self::IncorrectEndian) => true,
(Self::Handshake(_), Self::Handshake(_)) => true,
(Self::InvalidReply, Self::InvalidReply) => true,
#[cfg(feature = "comms")]
(Self::MethodError(_, _, _), Self::MethodError(_, _, _)) => true,
(Self::MissingField, Self::MissingField) => true,
(Self::InvalidGUID, Self::InvalidGUID) => true,
(Self::Unsupported, Self::Unsupported) => true,
#[cfg(feature = "comms")]
(Self::FDO(s), Self::FDO(o)) => s == o,
#[cfg(feature = "comms")]
(Self::DBus(s), Self::DBus(o)) => {
s.name() == o.name() && s.description() == o.description()
}
(Self::NameTaken, Self::NameTaken) => true,
(Self::InvalidMatchRule, Self::InvalidMatchRule) => true,
(Self::InvalidSerial, Self::InvalidSerial) => true,
(Self::InterfaceExists(s1, s2), Self::InterfaceExists(o1, o2)) => s1 == o1 && s2 == o2,
#[cfg(feature = "comms")]
(Self::Connection(_, a1), Self::Connection(_, a2)) => a1 == a2,
(_, _) => false,
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Error::InputOutput(e) => Some(e),
Error::Utf8(e) => Some(e),
#[cfg(feature = "comms")]
Error::FDO(e) => Some(e),
#[cfg(feature = "comms")]
Error::DBus(_) => None,
#[cfg(feature = "comms")]
Error::Connection(e, _) => Some(e),
Error::Failure(_) => None,
Error::IncorrectType => None,
Error::PaddingNot0(_) => None,
Error::UnknownFd => None,
Error::SignatureMismatch(_, _) => None,
Error::OutOfBounds => None,
Error::MaxDepthExceeded(_) => None,
Error::SignatureParse(_) => None,
Error::EmptyStructure => None,
Error::InvalidObjectPath => None,
Error::InvalidName(_) => None,
Error::InvalidNameConversion { .. } => None,
Error::InterfaceNotFound => None,
Error::Address(_) => None,
Error::InvalidField => None,
Error::ExcessData => None,
Error::IncorrectEndian => None,
Error::Handshake(_) => None,
Error::InvalidReply => None,
#[cfg(feature = "comms")]
Error::MethodError(_, _, _) => None,
Error::MissingField => None,
Error::InvalidGUID => None,
Error::Unsupported => None,
Error::NameTaken => None,
Error::InvalidMatchRule => None,
Error::MissingParameter(_) => None,
Error::InvalidSerial => None,
Error::InterfaceExists(_, _) => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Failure(e) => write!(f, "{e}"),
Error::InputOutput(e) => write!(f, "I/O error: {e}"),
Error::IncorrectType => write!(f, "incorrect type"),
Error::Utf8(e) => write!(f, "{e}"),
Error::PaddingNot0(b) => write!(f, "Unexpected non-0 padding byte `{b}`"),
Error::UnknownFd => write!(f, "File descriptor not in the given FD index"),
Error::SignatureMismatch(provided, expected) => write!(
f,
"Signature mismatch: got `{provided}`, expected {expected}",
),
Error::OutOfBounds => write!(
f,
"Out of bounds range specified",
),
Error::MaxDepthExceeded(max) => write!(f, "{max}"),
Error::SignatureParse(e) => write!(f, "{e}"),
Error::EmptyStructure => write!(f, "Attempted to create an empty structure"),
Error::InvalidObjectPath => write!(f, "Invalid object path"),
Error::InvalidName(s) => write!(f, "{s}"),
Error::InvalidNameConversion { from, to } => {
write!(f, "Invalid conversion from `{from}` to `{to}`")
}
Error::InterfaceNotFound => write!(f, "Interface not found"),
Error::Address(e) => write!(f, "address error: {e}"),
Error::InvalidField => write!(f, "invalid message field"),
Error::ExcessData => write!(f, "excess data"),
Error::IncorrectEndian => write!(f, "incorrect endian"),
Error::Handshake(e) => write!(f, "D-Bus handshake failed: {e}"),
Error::InvalidReply => write!(f, "Invalid D-Bus method reply"),
#[cfg(feature = "comms")]
Error::MethodError(name, detail, _reply) => write!(
f,
"{}: {}",
**name,
detail.as_ref().map(|s| s.as_str()).unwrap_or("no details")
),
Error::MissingField => write!(f, "A required field is missing from message headers"),
Error::InvalidGUID => write!(f, "Invalid GUID"),
Error::Unsupported => write!(f, "Connection support is lacking"),
#[cfg(feature = "comms")]
Error::FDO(e) => write!(f, "{e}"),
#[cfg(feature = "comms")]
Error::DBus(e) => write!(f, "{e}"),
Error::NameTaken => write!(f, "name already taken on the bus"),
Error::InvalidMatchRule => write!(f, "Invalid match rule string"),
Error::MissingParameter(p) => {
write!(f, "Parameter `{p}` was not specified but it is required")
}
Error::InvalidSerial => write!(f, "Serial number in the message header is 0"),
Error::InterfaceExists(i, p) => write!(f, "Interface `{i}` already exists at `{p}`"),
#[cfg(feature = "comms")]
Error::Connection(e, addr) => write!(f, "failed to connect to `{addr}`: {e}"),
}
}
}
impl Error {
#[cold]
pub(crate) fn signature_mismatch(signature: &Signature, expected: &str) -> Self {
Self::SignatureMismatch(signature.clone(), expected.to_string())
}
pub fn description(&self) -> Option<&str> {
match self {
Error::Failure(e) => Some(e),
Error::InputOutput(_) => Some("i/o error"),
Error::IncorrectType => Some("incorrect type"),
Error::Utf8(_) => Some("invalid UTF-8"),
Error::PaddingNot0(_) => Some("unexpected non-0 padding byte"),
Error::UnknownFd => Some("file descriptor not in the given FD index"),
Error::SignatureMismatch(_, _) => Some("signature mismatch"),
Error::OutOfBounds => Some("out of bounds range specified"),
Error::MaxDepthExceeded(_) => Some("maximum allowed container depth exceeded"),
Error::SignatureParse(_) => Some("invalid signature"),
Error::EmptyStructure => Some("attempted to create an empty structure"),
Error::InvalidObjectPath => Some("invalid object path"),
Error::InvalidName(s) => Some(s),
Error::InvalidNameConversion { .. } => Some("invalid name conversion"),
Error::InterfaceNotFound => Some("interface not found"),
Error::Address(e) => Some(e),
Error::InvalidField => Some("invalid field"),
Error::ExcessData => Some("excess data"),
Error::IncorrectEndian => Some("incorrect endian"),
Error::Handshake(e) => Some(e),
Error::InvalidReply => Some("invalid reply"),
#[cfg(feature = "comms")]
Error::MethodError(_, desc, _) => desc.as_deref(),
#[cfg(feature = "comms")]
Error::DBus(e) => e.description(),
Error::MissingField => Some("a required field is missing from message headers"),
Error::InvalidGUID => Some("invalid GUID"),
Error::Unsupported => Some("connection support is lacking"),
#[cfg(feature = "comms")]
Error::FDO(_) => Some("FDO error"),
Error::NameTaken => Some("name already taken on the bus"),
Error::InvalidMatchRule => Some("invalid match rule string"),
Error::MissingParameter(_) => Some("A required parameter is missing"),
Error::InvalidSerial => Some("serial number in the message header is 0"),
Error::InterfaceExists(_, _) => Some("interface already exists"),
#[cfg(feature = "comms")]
Error::Connection(_, _) => Some("could not connect to specified address"),
}
}
}
impl de::Error for Error {
fn custom<T>(msg: T) -> Error
where
T: fmt::Display,
{
Error::Failure(msg.to_string())
}
}
impl ser::Error for Error {
fn custom<T>(msg: T) -> Error
where
T: fmt::Display,
{
Error::Failure(msg.to_string())
}
}
impl From<io::Error> for Error {
fn from(val: io::Error) -> Self {
Error::InputOutput(Arc::new(val))
}
}
impl From<wire::signature::Error> for Error {
fn from(e: wire::signature::Error) -> Self {
Error::SignatureParse(e)
}
}
impl From<Infallible> for Error {
fn from(i: Infallible) -> Self {
match i {}
}
}
#[cfg(feature = "comms")]
impl From<fdo::Error> for Error {
fn from(val: fdo::Error) -> Self {
match val {
fdo::Error::ZBus(e) => e,
e => Error::FDO(Box::new(e)),
}
}
}
#[cfg(feature = "comms")]
impl From<Message> for Error {
fn from(message: Message) -> Error {
let header = message.header();
if header.primary().msg_type() != Type::Error {
return Error::InvalidReply;
}
if let Some(name) = header.error_name() {
let name: OwnedErrorName = name.to_owned().into();
let detail: Option<String> = message
.body()
.deserialize_ignore_signature::<&str>()
.ok()
.map(String::from);
let method_error = Error::MethodError(name, detail, message);
Error::from(fdo::Error::from(method_error))
} else {
Error::InvalidReply
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MaxDepthExceeded {
Structure,
Array,
Container,
}
impl fmt::Display for MaxDepthExceeded {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Structure => write!(
f,
"Maximum allowed depth for structures in encoding was exceeded"
),
Self::Array => write!(
f,
"Maximum allowed depth for arrays in encoding was exceeded"
),
Self::Container => write!(
f,
"Maximum allowed depth for containers in encoding was exceeded"
),
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(all(test, feature = "comms"))]
mod tests {
use crate::{Error, fdo, message::Message};
#[test]
fn error_reply_with_fdo_name() {
let error = error_reply(
"org.freedesktop.DBus.Error.ServiceUnknown",
&"no such service",
);
assert_eq!(
error,
Error::FDO(Box::new(fdo::Error::ServiceUnknown(
"no such service".to_string()
))),
);
}
#[test]
fn error_reply_with_fdo_name_and_no_description() {
let error = error_reply("org.freedesktop.DBus.Error.ServiceUnknown", &());
assert_eq!(
error,
Error::FDO(Box::new(fdo::Error::ServiceUnknown(String::new()))),
);
}
#[test]
fn error_reply_with_other_name() {
let error = error_reply("org.example.Error.Oops", &"oops");
let Error::MethodError(name, detail, _) = error else {
panic!("expected `Error::MethodError`, got {error:?}");
};
assert_eq!(name.as_str(), "org.example.Error.Oops");
assert_eq!(detail.as_deref(), Some("oops"));
}
fn error_reply<B>(name: &str, body: &B) -> Error
where
B: serde::Serialize + crate::DynamicType,
{
let call = Message::method_call("/", "foo")
.destination(":1.2")
.build(&())
.unwrap();
Message::error(&call.header(), name)
.build(body)
.unwrap()
.into()
}
}