zbus/
proxy_builder.rs

1use std::{collections::HashSet, convert::TryInto, marker::PhantomData, sync::Arc};
2
3use static_assertions::assert_impl_all;
4use zbus_names::{BusName, InterfaceName};
5use zvariant::{ObjectPath, Str};
6
7use crate::{Connection, Error, Proxy, ProxyInner, 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 ProxyBuilder<'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 ProxyBuilder<'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!(ProxyBuilder<'_>: Send, Sync, Unpin);
50
51impl<'a, T> ProxyBuilder<'a, T> {
52    /// Create a new [`ProxyBuilder`] for the given connection.
53    #[must_use]
54    pub fn new_bare(conn: &Connection) -> Self {
55        Self {
56            conn: conn.clone(),
57            destination: None,
58            path: None,
59            interface: None,
60            cache: CacheProperties::default(),
61            uncached_properties: None,
62            proxy_type: PhantomData,
63        }
64    }
65}
66
67impl<'a, T> ProxyBuilder<'a, T> {
68    /// Set the proxy destination address.
69    pub fn destination<D>(mut self, destination: D) -> Result<Self>
70    where
71        D: TryInto<BusName<'a>>,
72        D::Error: Into<Error>,
73    {
74        self.destination = Some(destination.try_into().map_err(Into::into)?);
75        Ok(self)
76    }
77
78    /// Set the proxy path.
79    pub fn path<P>(mut self, path: P) -> Result<Self>
80    where
81        P: TryInto<ObjectPath<'a>>,
82        P::Error: Into<Error>,
83    {
84        self.path = Some(path.try_into().map_err(Into::into)?);
85        Ok(self)
86    }
87
88    /// Set the proxy interface.
89    pub fn interface<I>(mut self, interface: I) -> Result<Self>
90    where
91        I: TryInto<InterfaceName<'a>>,
92        I::Error: Into<Error>,
93    {
94        self.interface = Some(interface.try_into().map_err(Into::into)?);
95        Ok(self)
96    }
97
98    /// Set the properties caching mode.
99    #[must_use]
100    pub fn cache_properties(mut self, cache: CacheProperties) -> Self {
101        self.cache = cache;
102        self
103    }
104
105    /// Specify a set of properties (by name) which should be excluded from caching.
106    #[must_use]
107    pub fn uncached_properties(mut self, properties: &[&'a str]) -> Self {
108        self.uncached_properties
109            .replace(properties.iter().map(|p| Str::from(*p)).collect());
110
111        self
112    }
113
114    pub(crate) fn build_internal(self) -> Result<Proxy<'a>> {
115        let conn = self.conn;
116        let destination = self
117            .destination
118            .ok_or(Error::MissingParameter("destination"))?;
119        let path = self.path.ok_or(Error::MissingParameter("path"))?;
120        let interface = self.interface.ok_or(Error::MissingParameter("interface"))?;
121        let cache = self.cache;
122        let uncached_properties = self.uncached_properties.unwrap_or_default();
123
124        Ok(Proxy {
125            inner: Arc::new(ProxyInner::new(
126                conn,
127                destination,
128                path,
129                interface,
130                cache,
131                uncached_properties,
132            )),
133        })
134    }
135
136    /// Build a proxy from the builder.
137    ///
138    /// # Errors
139    ///
140    /// If the builder is lacking the necessary parameters to build a proxy,
141    /// [`Error::MissingParameter`] is returned.
142    pub async fn build(self) -> Result<T>
143    where
144        T: From<Proxy<'a>>,
145    {
146        let cache_upfront = self.cache == CacheProperties::Yes;
147        let proxy = self.build_internal()?;
148
149        if cache_upfront {
150            proxy
151                .get_property_cache()
152                .expect("properties cache not initialized")
153                .ready()
154                .await?;
155        }
156
157        Ok(proxy.into())
158    }
159}
160
161impl<'a, T> ProxyBuilder<'a, T>
162where
163    T: ProxyDefault,
164{
165    /// Create a new [`ProxyBuilder`] for the given connection.
166    #[must_use]
167    pub fn new(conn: &Connection) -> Self {
168        Self {
169            conn: conn.clone(),
170            destination: Some(BusName::from_static_str(T::DESTINATION).expect("invalid bus name")),
171            path: Some(ObjectPath::from_static_str(T::PATH).expect("invalid default path")),
172            interface: Some(
173                InterfaceName::from_static_str(T::INTERFACE).expect("invalid interface name"),
174            ),
175            cache: CacheProperties::default(),
176            uncached_properties: None,
177            proxy_type: PhantomData,
178        }
179    }
180}
181
182/// Trait for the default associated values of a proxy.
183///
184/// The trait is automatically implemented by the [`dbus_proxy`] macro on your behalf, and may be
185/// later used to retrieve the associated constants.
186///
187/// [`dbus_proxy`]: attr.dbus_proxy.html
188pub trait ProxyDefault {
189    const INTERFACE: &'static str;
190    const DESTINATION: &'static str;
191    const PATH: &'static str;
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 = ProxyBuilder::<Proxy<'_>>::new_bare(&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}