use std::{collections::HashSet, marker::PhantomData, sync::Arc};
use crate::{
ObjectPath, Str,
names::{BusName, InterfaceName},
};
use crate::{Connection, Error, Proxy, Result, proxy::ProxyInner};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CacheProperties {
Yes,
No,
#[default]
Lazily,
}
#[derive(Debug)]
pub struct Builder<'a, T = ()> {
conn: Connection,
destination: Option<BusName<'a>>,
path: Option<ObjectPath<'a>>,
interface: Option<InterfaceName<'a>>,
proxy_type: PhantomData<T>,
cache: CacheProperties,
uncached_properties: Option<HashSet<Str<'a>>>,
error: Option<Error>,
}
impl<T> Clone for Builder<'_, T> {
fn clone(&self) -> Self {
Self {
conn: self.conn.clone(),
destination: self.destination.clone(),
path: self.path.clone(),
interface: self.interface.clone(),
cache: self.cache,
uncached_properties: self.uncached_properties.clone(),
proxy_type: PhantomData,
error: self.error.clone(),
}
}
}
impl<'a, T> Builder<'a, T> {
#[must_use]
pub fn destination<D>(mut self, destination: D) -> Self
where
D: TryInto<BusName<'a>>,
D::Error: Into<Error>,
{
match destination.try_into() {
Ok(destination) => self.destination = Some(destination),
Err(e) => self.record(e.into()),
}
self
}
#[must_use]
pub fn path<P>(mut self, path: P) -> Self
where
P: TryInto<ObjectPath<'a>>,
P::Error: Into<Error>,
{
match path.try_into() {
Ok(path) => self.path = Some(path),
Err(e) => self.record(e.into()),
}
self
}
#[must_use]
pub fn interface<I>(mut self, interface: I) -> Self
where
I: TryInto<InterfaceName<'a>>,
I::Error: Into<Error>,
{
match interface.try_into() {
Ok(interface) => self.interface = Some(interface),
Err(e) => self.record(e.into()),
}
self
}
#[must_use]
pub fn cache_properties(mut self, cache: CacheProperties) -> Self {
self.cache = cache;
self
}
#[must_use]
pub fn uncached_properties(mut self, properties: &[&'a str]) -> Self {
self.uncached_properties
.replace(properties.iter().map(|p| Str::from(*p)).collect());
self
}
pub(crate) fn build_internal(self) -> Result<Proxy<'a>> {
if let Some(error) = self.error {
return Err(error);
}
let conn = self.conn;
let destination = self
.destination
.ok_or(Error::MissingParameter("destination"))?;
let path = self.path.ok_or(Error::MissingParameter("path"))?;
let interface = self.interface.ok_or(Error::MissingParameter("interface"))?;
let cache = self.cache;
let uncached_properties = self.uncached_properties.unwrap_or_default();
Ok(Proxy {
inner: Arc::new(ProxyInner::new(
conn,
destination,
path,
interface,
cache,
uncached_properties,
)),
})
}
pub async fn build(self) -> Result<T>
where
T: From<Proxy<'a>> + super::Defaults,
{
let cache_upfront = T::HAS_PROPERTIES && self.cache == CacheProperties::Yes;
let proxy = self.build_internal()?;
if cache_upfront {
proxy
.get_property_cache()
.expect("properties cache not initialized")
.ready()
.await?;
}
Ok(proxy.into())
}
fn record(&mut self, error: Error) {
if self.error.is_none() {
self.error = Some(error);
}
}
}
impl<T> Builder<'_, T>
where
T: super::Defaults,
{
#[must_use]
pub fn new(conn: &Connection) -> Self {
Self {
conn: conn.clone(),
destination: T::DESTINATION.clone(),
path: T::PATH.clone(),
interface: T::INTERFACE.clone(),
cache: CacheProperties::default(),
uncached_properties: None,
proxy_type: PhantomData,
error: None,
}
}
}
#[cfg(all(test, any(feature = "default-rt", feature = "tokio")))]
mod tests {
use super::*;
use test_log::test;
#[test]
#[ntest::timeout(15000)]
fn builder() {
crate::utils::block_on(builder_async());
}
async fn builder_async() {
let conn = Connection::session().await.unwrap();
let builder = Builder::<Proxy<'_>>::new(&conn)
.destination("org.freedesktop.DBus")
.path("/some/path")
.interface("org.freedesktop.Interface")
.cache_properties(CacheProperties::No);
assert!(matches!(
builder.clone().destination.unwrap(),
BusName::Unique(_),
));
let proxy = builder.build().await.unwrap();
assert!(matches!(proxy.inner.destination, BusName::Unique(_)));
let destination = BusName::try_from("org.freedesktop.DBus").unwrap();
let path = ObjectPath::try_from("/some/path").unwrap();
let interface = InterfaceName::try_from("org.freedesktop.Interface").unwrap();
let proxy = Builder::<Proxy<'_>>::new(&conn)
.destination(&destination)
.path(path.clone())
.interface(interface.clone())
.build()
.await
.unwrap();
assert_eq!(proxy.inner.path, path);
assert_eq!(proxy.inner.interface, interface);
let err = Builder::<Proxy<'_>>::new(&conn)
.destination("not a bus name")
.path("/some/path")
.interface("org.freedesktop.Interface")
.build()
.await
.unwrap_err();
assert_eq!(
err.to_string(),
BusName::try_from("not a bus name").unwrap_err().to_string(),
);
let err = Builder::<Proxy<'_>>::new(&conn)
.destination("not a bus name")
.destination("org.freedesktop.DBus")
.path("/some/path")
.interface("org.freedesktop.Interface")
.build()
.await
.unwrap_err();
assert_eq!(
err.to_string(),
BusName::try_from("not a bus name").unwrap_err().to_string(),
);
}
}