1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
use zbus::DBusError;

use crate::desktop::{dynamic_launcher::UnexpectedIconError, request::ResponseError};

/// An error type that describes the various DBus errors.
///
/// See <https://github.com/flatpak/xdg-desktop-portal/blob/master/src/xdp-utils.h#L119-L127>.
#[allow(missing_docs)]
#[derive(DBusError, Debug)]
#[zbus(prefix = "org.freedesktop.portal.Error")]
pub enum PortalError {
    #[zbus(error)]
    /// ZBus specific error.
    ZBus(zbus::Error),
    /// Request failed.
    Failed(String),
    /// Invalid arguments passed.
    InvalidArgument(String),
    /// Not found.
    NotFound(String),
    /// Exists already.
    Exist(String),
    /// Method not allowed to be called.
    NotAllowed(String),
    /// Request cancelled.
    Cancelled(String),
    /// Window destroyed.
    WindowDestroyed(String),
}

#[derive(Debug)]
#[non_exhaustive]
/// The error type for ashpd.
pub enum Error {
    /// The portal request didn't succeed.
    Response(ResponseError),
    /// Something Failed on the portal request.
    Portal(PortalError),
    /// A zbus::fdo specific error.
    Zbus(zbus::Error),
    /// A signal returned no response.
    NoResponse,
    /// Failed to parse a string into an enum variant
    ParseError(&'static str),
    /// Input/Output
    IO(std::io::Error),
    /// A pipewire error
    #[cfg(feature = "pipewire")]
    Pipewire(pipewire::Error),
    /// Invalid AppId
    ///
    /// See <https://developer.gnome.org/documentation/tutorials/application-id.html#rules-for-application-ids>
    InvalidAppID,
    /// An error indicating that an interior nul byte was found
    NulTerminated(usize),
    /// Requires a newer interface version.
    ///
    /// The inner fields are the required version and the version advertised by
    /// the interface.
    RequiresVersion(u32, u32),
    /// Returned when the portal wasn't found. Either the user has no portals
    /// frontend installed or the frontend doesn't support the used portal.
    PortalNotFound(zbus::names::OwnedInterfaceName),
    /// An error indicating that a Icon::Bytes was expected but wrong type was
    /// passed
    UnexpectedIcon,
}

impl std::error::Error for Error {}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Response(e) => f.write_str(&format!("Portal request didn't succeed: {e}")),
            Self::Zbus(e) => f.write_str(&format!("ZBus Error: {e}")),
            Self::Portal(e) => f.write_str(&format!("Portal request failed: {e}")),
            Self::NoResponse => f.write_str("Portal error: no response"),
            Self::IO(e) => f.write_str(&format!("IO: {e}")),
            #[cfg(feature = "pipewire")]
            Self::Pipewire(e) => f.write_str(&format!("Pipewire: {e}")),
            Self::ParseError(e) => f.write_str(e),
            Self::InvalidAppID => f.write_str("Invalid app id"),
            Self::NulTerminated(u) => write!(f, "Nul byte found in provided data at position {u}"),
            Self::RequiresVersion(required, current) => write!(
                f,
                "This interface requires version {required}, but {current} is available"
            ),
            Self::PortalNotFound(portal) => {
                write!(f, "A portal frontend implementing `{portal}` was not found")
            }
            Self::UnexpectedIcon => write!(
                f,
                "Expected icon of type Icon::Bytes but a different type was used."
            ),
        }
    }
}

impl From<ResponseError> for Error {
    fn from(e: ResponseError) -> Self {
        Self::Response(e)
    }
}

impl From<PortalError> for Error {
    fn from(e: PortalError) -> Self {
        Self::Portal(e)
    }
}

#[cfg(feature = "pipewire")]
impl From<pipewire::Error> for Error {
    fn from(e: pipewire::Error) -> Self {
        Self::Pipewire(e)
    }
}

impl From<zbus::fdo::Error> for Error {
    fn from(e: zbus::fdo::Error) -> Self {
        Self::Zbus(zbus::Error::FDO(Box::new(e)))
    }
}

impl From<zbus::Error> for Error {
    fn from(e: zbus::Error) -> Self {
        match &e {
            zbus::Error::MethodError(_name, Some(details), _reply) => {
                // This is really a gross hack, needs to find a better way but it works.
                let iface = details
                    .trim_start_matches("No such interface")
                    .trim_end_matches("on object at path /org/freedesktop/portal/desktop")
                    .trim()
                    .trim_matches('`')
                    .trim_matches('“')
                    .trim_matches('”');
                match zbus::names::OwnedInterfaceName::try_from(iface) {
                    Ok(iface) => Self::PortalNotFound(iface),
                    Err(_err) => {
                        #[cfg(feature = "tracing")]
                        {
                            tracing::warn!("Hack! The parsing of the iface name has failed: iface {iface}, error details {details}")
                        };
                        Self::Zbus(e)
                    }
                }
            }
            _ => Self::Zbus(e),
        }
    }
}

impl From<zbus::zvariant::Error> for Error {
    fn from(e: zbus::zvariant::Error) -> Self {
        Self::Zbus(zbus::Error::Variant(e))
    }
}

impl From<std::io::Error> for Error {
    fn from(e: std::io::Error) -> Self {
        Self::IO(e)
    }
}

impl From<UnexpectedIconError> for Error {
    fn from(_: UnexpectedIconError) -> Self {
        Self::UnexpectedIcon
    }
}