zbus/proxy/
builder.rs

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