ashpd/desktop/
request.rs

1use std::{
2    collections::HashMap,
3    fmt::{self, Debug},
4    marker::PhantomData,
5    sync::Mutex,
6};
7
8use futures_util::StreamExt;
9use serde::{
10    de::{self, Error as SeError, Visitor},
11    ser::SerializeTuple,
12    Deserialize, Deserializer, Serialize,
13};
14use zbus::{
15    proxy::SignalStream,
16    zvariant::{ObjectPath, Type, Value},
17};
18
19use crate::{desktop::HandleToken, proxy::Proxy, Error};
20
21/// A typical response returned by the [`Request::response`].
22/// of a [`Request`].
23#[derive(Debug, Type)]
24#[zvariant(signature = "(ua{sv})")]
25pub enum Response<T> {
26    /// Success, the request is carried out.
27    Ok(T),
28    /// The user cancelled the request or something else happened.
29    Err(ResponseError),
30}
31
32#[cfg(feature = "backend")]
33#[cfg_attr(docsrs, doc(cfg(feature = "backend")))]
34impl<T> Response<T> {
35    /// The corresponding response type.
36    pub fn response_type(self) -> ResponseType {
37        match self {
38            Self::Ok(_) => ResponseType::Success,
39            Self::Err(err) => match err {
40                ResponseError::Cancelled => ResponseType::Cancelled,
41                ResponseError::Other => ResponseType::Other,
42            },
43        }
44    }
45
46    /// A successful response.
47    pub fn ok(inner: T) -> Self {
48        Self::Ok(inner)
49    }
50
51    /// Cancelled request.
52    pub fn cancelled() -> Self {
53        Self::Err(ResponseError::Cancelled)
54    }
55
56    /// Another error.
57    pub fn other() -> Self {
58        Self::Err(ResponseError::Other)
59    }
60}
61
62impl<'de, T> Deserialize<'de> for Response<T>
63where
64    T: for<'d> Deserialize<'d> + Type,
65{
66    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
67    where
68        D: Deserializer<'de>,
69    {
70        struct ResponseVisitor<T>(PhantomData<fn() -> (ResponseType, T)>);
71
72        impl<'de, T> Visitor<'de> for ResponseVisitor<T>
73        where
74            T: Deserialize<'de>,
75        {
76            type Value = (ResponseType, Option<T>);
77
78            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
79                write!(
80                    formatter,
81                    "a tuple composed of the response status along with the response"
82                )
83            }
84
85            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
86            where
87                A: de::SeqAccess<'de>,
88            {
89                let type_: ResponseType = seq.next_element()?.ok_or_else(|| A::Error::custom(
90                    "Failed to deserialize the response. Expected a numeric (u) value as the first item of the returned tuple",
91                ))?;
92                if type_ == ResponseType::Success {
93                    let data: T = seq.next_element()?.ok_or_else(|| A::Error::custom(
94                        "Failed to deserialize the response. Expected a vardict (a{sv}) with the returned results",
95                    ))?;
96                    Ok((type_, Some(data)))
97                } else {
98                    Ok((type_, None))
99                }
100            }
101        }
102
103        let visitor = ResponseVisitor::<T>(PhantomData);
104        let response: (ResponseType, Option<T>) = deserializer.deserialize_tuple(2, visitor)?;
105        Ok(response.into())
106    }
107}
108
109impl<T> Serialize for Response<T>
110where
111    T: Serialize + Type,
112{
113    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
114    where
115        S: serde::Serializer,
116    {
117        let mut map = serializer.serialize_tuple(2)?;
118        match self {
119            Self::Err(err) => {
120                map.serialize_element(&ResponseType::from(*err))?;
121                map.serialize_element(&HashMap::<&str, Value<'_>>::new())?;
122            }
123            Self::Ok(response) => {
124                map.serialize_element(&ResponseType::Success)?;
125                map.serialize_element(response)?;
126            }
127        };
128        map.end()
129    }
130}
131
132#[doc(hidden)]
133impl<T> From<(ResponseType, Option<T>)> for Response<T> {
134    fn from(f: (ResponseType, Option<T>)) -> Self {
135        match f.0 {
136            ResponseType::Success => {
137                Response::Ok(f.1.expect("Expected a valid response, found nothing."))
138            }
139            ResponseType::Cancelled => Response::Err(ResponseError::Cancelled),
140            ResponseType::Other => Response::Err(ResponseError::Other),
141        }
142    }
143}
144
145#[derive(Debug, Copy, PartialEq, Eq, Hash, Clone)]
146/// An error returned a portal request caused by either the user cancelling the
147/// request or something else.
148pub enum ResponseError {
149    /// The user canceled the request.
150    Cancelled,
151    /// Something else happened.
152    Other,
153}
154
155impl std::error::Error for ResponseError {}
156
157impl std::fmt::Display for ResponseError {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        match self {
160            Self::Cancelled => f.write_str("Cancelled"),
161            Self::Other => f.write_str("Other"),
162        }
163    }
164}
165
166#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Type)]
167/// Possible responses.
168pub enum ResponseType {
169    /// Success, the request is carried out.
170    Success = 0,
171    /// The user cancelled the interaction.
172    Cancelled = 1,
173    /// The user interaction was ended in some other way.
174    Other = 2,
175}
176
177#[doc(hidden)]
178impl From<ResponseError> for ResponseType {
179    fn from(err: ResponseError) -> Self {
180        match err {
181            ResponseError::Other => Self::Other,
182            ResponseError::Cancelled => Self::Cancelled,
183        }
184    }
185}
186
187/// The Request interface is shared by all portal interfaces.
188///
189/// When a portal method is called, the reply includes a handle (i.e. object
190/// path) for a Request object, which will stay alive for the duration of the
191/// user interaction related to the method call.
192///
193/// The portal indicates that a portal request interaction is over by emitting
194/// the "Response" signal on the Request object.
195///
196/// The application can abort the interaction calling
197/// [`close()`][`Request::close`] on the Request object.
198///
199/// Wrapper of the DBus interface: [`org.freedesktop.portal.Request`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Request.html).
200#[doc(alias = "org.freedesktop.portal.Request")]
201pub struct Request<T>(
202    Proxy<'static>,
203    SignalStream<'static>,
204    Mutex<Option<Result<T, Error>>>,
205    PhantomData<T>,
206)
207where
208    T: for<'de> Deserialize<'de> + Type + Debug;
209
210impl<T> Request<T>
211where
212    T: for<'de> Deserialize<'de> + Type + Debug,
213{
214    pub(crate) async fn new<P>(path: P) -> Result<Request<T>, Error>
215    where
216        P: TryInto<ObjectPath<'static>>,
217        P::Error: Into<zbus::Error>,
218    {
219        let proxy = Proxy::new_desktop_with_path("org.freedesktop.portal.Request", path).await?;
220        // Start listening for a response signal the moment request is created
221        let stream = proxy.receive_signal("Response").await?;
222        Ok(Self(proxy, stream, Default::default(), PhantomData))
223    }
224
225    pub(crate) async fn from_unique_name(handle_token: &HandleToken) -> Result<Request<T>, Error> {
226        let path =
227            Proxy::unique_name("/org/freedesktop/portal/desktop/request", handle_token).await?;
228        #[cfg(feature = "tracing")]
229        tracing::info!("Creating a org.freedesktop.portal.Request {}", path);
230        Self::new(path).await
231    }
232
233    pub(crate) async fn prepare_response(&mut self) -> Result<(), Error> {
234        let message = self.1.next().await.ok_or(Error::NoResponse)?;
235        #[cfg(feature = "tracing")]
236        tracing::info!("Received signal 'Response' on '{}'", self.0.interface());
237        let response = match message.body().deserialize::<Response<T>>()? {
238            Response::Err(e) => Err(e.into()),
239            Response::Ok(r) => Ok(r),
240        };
241        #[cfg(feature = "tracing")]
242        tracing::debug!("Received response {:#?}", response);
243        let r = response as Result<T, Error>;
244        *self.2.get_mut().unwrap() = Some(r);
245        Ok(())
246    }
247
248    /// The corresponding response if the request was successful.
249    ///
250    /// # Specifications
251    ///
252    /// See also [`Response`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Request.html#org-freedesktop-portal-request-response).
253    pub fn response(&self) -> Result<T, Error> {
254        // It should be safe to unwrap here as we are sure we have received a response
255        // by the time the user calls response
256        self.2.lock().unwrap().take().unwrap()
257    }
258
259    /// Closes the portal request to which this object refers and ends all
260    /// related user interaction (dialogs, etc). A Response signal will not
261    /// be emitted in this case.
262    ///
263    /// # Specifications
264    ///
265    /// See also [`Close`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Request.html#org-freedesktop-portal-request-close).
266    #[doc(alias = "Close")]
267    pub async fn close(&self) -> Result<(), Error> {
268        self.0.call("Close", &()).await
269    }
270
271    /// Return the object path of the request.
272    pub(crate) fn path(&self) -> &ObjectPath<'_> {
273        self.0.path()
274    }
275}
276
277impl<T> Debug for Request<T>
278where
279    T: for<'de> Deserialize<'de> + Type + Debug,
280{
281    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
282        f.debug_tuple("Request")
283            .field(&self.path().as_str())
284            .finish()
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use zbus::zvariant::Value;
291
292    use super::*;
293
294    #[test]
295    fn response_signature() {
296        use crate::desktop::account::UserInformation;
297        assert_eq!(
298            <(ResponseType, HashMap<&str, Value<'_>>)>::SIGNATURE,
299            Response::<()>::SIGNATURE,
300        );
301        assert_eq!(
302            <(ResponseType, UserInformation)>::SIGNATURE,
303            Response::<UserInformation>::SIGNATURE,
304        );
305        assert_eq!(Response::<()>::SIGNATURE, "(ua{sv})");
306    }
307}