zbus/proxy/
builder.rs

1use std::{collections::HashSet, marker::PhantomData, sync::Arc};
2
3use static_assertions::assert_impl_all;
4use zbus_names::{BusName, InterfaceName};
5use zvariant::{ObjectPath, Str};
6
7use crate::{proxy::ProxyInner, Connection, Error, Proxy, Result};
8
9/// The properties caching mode.
10#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum CacheProperties {
13    /// Cache properties. The properties will be cached upfront as part of the proxy
14    /// creation.
15    Yes,
16    /// Don't cache properties.
17    No,
18    /// Cache properties but only populate the cache on the first read of a property (default).
19    #[default]
20    Lazily,
21}
22
23/// Builder for proxies.
24#[derive(Debug)]
25pub struct Builder<'a, T = ()> {
26    conn: Connection,
27    destination: Option<BusName<'a>>,
28    path: Option<ObjectPath<'a>>,
29    interface: Option<InterfaceName<'a>>,
30    proxy_type: PhantomData<T>,
31    cache: CacheProperties,
32    uncached_properties: Option<HashSet<Str<'a>>>,
33}
34
35impl<'a, T> Clone for Builder<'a, T> {
36    fn clone(&self) -> Self {
37        Self {
38            conn: self.conn.clone(),
39            destination: self.destination.clone(),
40            path: self.path.clone(),
41            interface: self.interface.clone(),
42            cache: self.cache,
43            uncached_properties: self.uncached_properties.clone(),
44            proxy_type: PhantomData,
45        }
46    }
47}
48
49assert_impl_all!(Builder<'_>: Send, Sync, Unpin);
50
51impl<'a, T> Builder<'a, T> {
52    /// Set the proxy destination address.
53    pub fn destination<D>(mut self, destination: D) -> Result<Self>
54    where
55        D: TryInto<BusName<'a>>,
56        D::Error: Into<Error>,
57    {
58        self.destination = Some(destination.try_into().map_err(Into::into)?);
59        Ok(self)
60    }
61
62    /// Set the proxy path.
63    pub fn path<P>(mut self, path: P) -> Result<Self>
64    where
65        P: TryInto<ObjectPath<'a>>,
66        P::Error: Into<Error>,
67    {
68        self.path = Some(path.try_into().map_err(Into::into)?);
69        Ok(self)
70    }
71
72    /// Set the proxy interface.
73    pub fn interface<I>(mut self, interface: I) -> Result<Self>
74    where
75        I: TryInto<InterfaceName<'a>>,
76        I::Error: Into<Error>,
77    {
78        self.interface = Some(interface.try_into().map_err(Into::into)?);
79        Ok(self)
80    }
81
82    /// Set the properties caching mode.
83    #[must_use]
84    pub fn cache_properties(mut self, cache: CacheProperties) -> Self {
85        self.cache = cache;
86        self
87    }
88
89    /// Specify a set of properties (by name) which should be excluded from caching.
90    #[must_use]
91    pub fn uncached_properties(mut self, properties: &[&'a str]) -> Self {
92        self.uncached_properties
93            .replace(properties.iter().map(|p| Str::from(*p)).collect());
94
95        self
96    }
97
98    pub(crate) fn build_internal(self) -> Result<Proxy<'a>> {
99        let conn = self.conn;
100        let destination = self
101            .destination
102            .ok_or(Error::MissingParameter("destination"))?;
103        let path = self.path.ok_or(Error::MissingParameter("path"))?;
104        let interface = self.interface.ok_or(Error::MissingParameter("interface"))?;
105        let cache = self.cache;
106        let uncached_properties = self.uncached_properties.unwrap_or_default();
107
108        Ok(Proxy {
109            inner: Arc::new(ProxyInner::new(
110                conn,
111                destination,
112                path,
113                interface,
114                cache,
115                uncached_properties,
116            )),
117        })
118    }
119
120    /// Build a proxy from the builder.
121    ///
122    /// # Errors
123    ///
124    /// If the builder is lacking the necessary parameters to build a proxy,
125    /// [`Error::MissingParameter`] is returned.
126    pub async fn build(self) -> Result<T>
127    where
128        T: From<Proxy<'a>>,
129    {
130        let cache_upfront = self.cache == CacheProperties::Yes;
131        let proxy = self.build_internal()?;
132
133        if cache_upfront {
134            proxy
135                .get_property_cache()
136                .expect("properties cache not initialized")
137                .ready()
138                .await?;
139        }
140
141        Ok(proxy.into())
142    }
143}
144
145impl<'a, T> Builder<'a, T>
146where
147    T: ProxyDefault,
148{
149    /// Create a new [`Builder`] for the given connection.
150    #[must_use]
151    pub fn new(conn: &Connection) -> Self {
152        Self {
153            conn: conn.clone(),
154            destination: T::DESTINATION
155                .map(|d| BusName::from_static_str(d).expect("invalid bus name")),
156            path: T::PATH.map(|p| ObjectPath::from_static_str(p).expect("invalid default path")),
157            interface: T::INTERFACE
158                .map(|i| InterfaceName::from_static_str(i).expect("invalid interface name")),
159            cache: CacheProperties::default(),
160            uncached_properties: None,
161            proxy_type: PhantomData,
162        }
163    }
164
165    /// Create a new [`Builder`] for the given connection.
166    #[must_use]
167    #[deprecated(
168        since = "4.0.0",
169        note = "use `Builder::new` instead, which is now generic over the proxy type"
170    )]
171    pub fn new_bare(conn: &Connection) -> Self {
172        Self::new(conn)
173    }
174}
175
176/// Trait for the default associated values of a proxy.
177///
178/// The trait is automatically implemented by the [`dbus_proxy`] macro on your behalf, and may be
179/// later used to retrieve the associated constants.
180///
181/// [`dbus_proxy`]: attr.dbus_proxy.html
182pub trait ProxyDefault {
183    const INTERFACE: Option<&'static str>;
184    const DESTINATION: Option<&'static str>;
185    const PATH: Option<&'static str>;
186}
187
188impl ProxyDefault for Proxy<'_> {
189    const INTERFACE: Option<&'static str> = None;
190    const DESTINATION: Option<&'static str> = None;
191    const PATH: Option<&'static str> = None;
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use test_log::test;
198
199    #[test]
200    #[ntest::timeout(15000)]
201    fn builder() {
202        crate::utils::block_on(builder_async());
203    }
204
205    async fn builder_async() {
206        let conn = Connection::session().await.unwrap();
207
208        let builder = Builder::<Proxy<'_>>::new(&conn)
209            .destination("org.freedesktop.DBus")
210            .unwrap()
211            .path("/some/path")
212            .unwrap()
213            .interface("org.freedesktop.Interface")
214            .unwrap()
215            .cache_properties(CacheProperties::No);
216        assert!(matches!(
217            builder.clone().destination.unwrap(),
218            BusName::Unique(_),
219        ));
220        let proxy = builder.build().await.unwrap();
221        assert!(matches!(proxy.inner.destination, BusName::Unique(_)));
222    }
223}