Proxy API discussion
Before I do some cleanups and propose the proxy API, I could take some feedback. Please focus on externally visible API, all the inner stuff we can evolve later. Also, I focus exclusively on owned version for now, because it is more convenient to use and that's what I need first. I think we can derive a borrowed / callback-based version in a later iteration. It is also missing signals, which will have to be registered through some kind of dispatcher, or other API which are to be defined.
Does the overall Proxy
API look ok? This is mostly an internal API for the derive macro to use, but it will be public, and may be used directly if prefered (not a good idea I think).
https://gitlab.freedesktop.org/elmarco/zbus/-/blob/zbus-wip/zbus/examples/server/proxy.rs
pub type ProxyResult<T> = std::result::Result<T, ProxyError>;
impl<'c> Proxy<'c> {
pub fn new(
conn: &'c Connection,
destination: &str,
path: &str,
interface: &str,
) -> ProxyResult<Self>
pub fn introspect(&self) -> ProxyResult<String>
pub fn try_get<T>(&self, property_name: &str) -> ProxyResult<T>
where
T: TryFrom<OwnedValue>
pub fn try_set<'a, T: 'a>(&self, property_name: &str, value: T) -> ProxyResult<()>
where
T: Into<Value<'a>>
pub fn call<B, R>(&self, method_name: &str, body: &B) -> ProxyResult<R>
where
B: serde::ser::Serialize + zvariant::Type,
R: serde::de::DeserializeOwned + zvariant::Type
}
- should that be on top-level namespace, or ::proxy or else?
- should it use a seperate Error/Result or reuse Connection's?
For the macro/derive part, I have looked at various DBus APIs (fdo, systemd and polkit + my own experience). A good example usage I propose is: https://gitlab.freedesktop.org/elmarco/zbus/-/blob/zbus-wip/zbus/examples/server/polkit1.rs#L299
#[DBusProxy(
interface = "org.freedesktop.PolicyKit1.Authority",
default_service = "org.freedesktop.PolicyKit1",
default_path = "/org/freedesktop/PolicyKit1/Authority"
)]
trait Authority {
fn CheckAuthorization(
subject: &Subject,
action_id: &str,
details: std::collections::HashMap<&str, &str>,
flags: CheckAuthorizationFlags,
cancellation_id: &str,
) -> AuthorizationResult;
#[DBusProxy(property)]
fn BackendFeatures() -> AuthorityFeatures;
/// some other example for getter/setters.
#[DBusProxy(property)]
fn KExecWatchdogUSec() -> u64;
/// A property method that has input args is a setter. By default, it will strip the Set from the name for the property name
#[DBusProxy(property)]
fn SetKExecWatchdogUSec(value: u64);
}
The actual derived impl looks something like:
impl Authority {
// constructors:
fn new(c: &Connection) -> Self;
fn new_for(c: &Connection, dest: &str, path &str) -> Self;
// method call, &self is appended, ProxyResult wraps the returned value
fn CheckAuthorization(
&self,
subject: &Subject,
action_id: &str,
details: std::collections::HashMap<&str, &str>,
flags: CheckAuthorizationFlags,
cancellation_id: &str,
) -> ProxyResult<AuthorizationResult> {
self.call("CheckAuthorization", &(subject, action_id,...))
}
// same in snake_case
fn check_authorization(
&self,
subject: &Subject,
action_id: &str,
details: std::collections::HashMap<&str, &str>,
flags: CheckAuthorizationFlags,
cancellation_id: &str,
) -> ProxyResult<AuthorizationResult>;
fn BackendFeatures(&self) -> ProxyResult<AuthorityFeatures> {
self.try_get("BackendFeatures")
}
fn backend_features(&self) -> ProxyResult<AuthorityFeatures>
fn SetKExecWatchdogUSec(value: u64) -> ProxyResult<()> {
self.try_set("KExecWatchdogUSec", &value)
}
fn set_kexec_watchdog_usec(value: u64) -> ProxyResult<()>; // actually, my snake-case function is buggy atm :)
In the trait,
- should it have the &self explicitly
- similarly, should it have the ProxyResult explicetly too? (both would be quite verbose and repetitive, so I decided not to have them by default, and only reflect DBus API)
- again, to better reflect the DBus API, I prefered to use the casing used over DBus by default when declaring the trait, and let the macro derive the rest. Should we instead have snake_case method converted to CamelCase? (with exceptions being edited with additional attributes?)
The traits can be automatically generated from introspection XML, only higher level types (structs, flags) are done manually, since the schema doesn't expose such details.
Perhaps a trait is not the most appropriate to declare the DBus API, but then what should it be? Some adhoc rust-derived description language?
Any other comment?