rustdbusserde

Rust zbus call_method with complext structs that include zvariant::Value


I saw this example on the freedesktop.org pages:

use std::collections::HashMap;
use std::error::Error;

use zbus::Connection;
use zvariant::Value;

fn main() -> Result<(), Box<dyn Error>> {
    let connection = Connection::new_session()?;

    let m = connection.call_method(
        Some("org.freedesktop.Notifications"),
        "/org/freedesktop/Notifications",
        Some("org.freedesktop.Notifications"),
        "Notify",
        &("my-app", 0u32, "dialog-information", "A summary", "Some body",
          vec![""; 0], HashMap::<&str, &Value>::new(), 5000),
    )?;
    let reply: u32 = m.body().unwrap();
    dbg!(reply);
    Ok(())
}

And I wanted to refactor it so I could just call another dbus service, one that I have implemented in C. The idea is that given a boolean value, I want to call one or another with different arguments. But I have found that the refactor of the original code, trying to change the arguments tuple to a struct, and making the args into a zvariant::Value is more difficult than I expected.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::error::Error;

//use zbus::{Connection, zvariant::Value};
use zbus::Connection;
use zvariant::{DynamicType, OwnedValue, Structure, Type, Value, serialized::Context};

#[derive(Debug, PartialEq, Deserialize, Serialize, Type)]
#[zvariant(signature = "s")]
struct NetConfigMethodArgs {
    interface: String,
    ip: String,
}

const NETCONFIG_SERVICE: &str = "com.example.NetConfig";
const NETCONFIG_PATH: &str = "/com/example/NetConfig";
const NETCONFIG_OBJECT: &str = "com.example.Netconfig";
const NETCONFIG_METHOD: &str = "SetInterfaceIP";

#[derive(Debug, PartialEq, Deserialize, Serialize, Type)]
#[zvariant(signature = "susssasa{sv}i")]
struct NotificationsMethodArgs<> {
    app_name: String,
    replaces_id: u32,
    app_icon: String,
    summary: String,
    body: String,
    actions: Vec<String>,
    hints: HashMap<&str, &Value>,
    timeout: i32,
}
const NOTIFICATIONS_SERVICE: &str = "org.freedesktop.Notifications";
const NOTIFICATIONS_PATH: &str = "/org/freedesktop/Notifications";
const NOTIFICATIONS_OBJECT: &str = "org.freedesktop.Notifications";
const NOTIFICATIONS_METHOD: &str = "Notify";

// Although we use `tokio` here, you can use any async runtime of choice.
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let connection = Connection::session().await?;

    let mut service = NETCONFIG_SERVICE;
    let mut path = NETCONFIG_PATH;
    let mut object = NETCONFIG_OBJECT;
    let mut method = NETCONFIG_METHOD;
    let netconfigargs = NetConfigMethodArgs {
        interface: String::from("enxc0470e9a117"),
        ip: String::from("192.168.1.141"),
    };
    let args = Value::new(netconfigargs);

    if true {
        service = NOTIFICATIONS_SERVICE;
        path = NOTIFICATIONS_PATH;
        object = NOTIFICATIONS_OBJECT;
        method = NOTIFICATIONS_METHOD;
        let notificationsargs = NotificationsMethodArgs {
            app_name: String::from("my-app"),
            replaces_id: 0u32,
            app_icon: String::from("dialog-information"),
            summary: String::from("A summary"),
            body: String::from("Some body"),
            actions: vec!["".to_string(); 0],
            hints: HashMap::<&str, &Value>::new(),
            timeout: 5000,
        };
    }

    let m = connection
        .call_method(Some(service), path, Some(object), method, &args)
        .await?;
    let reply: u32 = m.body().deserialize().unwrap();
    dbg!(reply);
    Ok(())
}

Depending on what things I do, the compiler complaints in different ways. But the most interesting to me for resolve are these two:

Diagnostics:

the trait bound `zvariant::Structure<'_>: From<NetConfigMethodArgs>` is not satisfied    the following other types implement trait `From<T>`:
     `zvariant::Structure<'_>` implements `From<(T0, T1)>`
     `zvariant::Structure<'_>` implements `From<(T0, T1, T2)>`
     `zvariant::Structure<'_>` implements `From<(T0, T1, T2, T3)>`
     `zvariant::Structure<'_>` implements `From<(T0, T1, T2, T3, T4)>`
     `zvariant::Structure<'_>` implements `From<(T0, T1, T2, T3, T4, T5)>`
     `zvariant::Structure<'_>` implements `From<(T0, T1, T2, T3, T4, T5, T6)>`
     `zvariant::Structure<'_>` implements `From<(T0, T1, T2, T3, T4, T5, T6, T7)>`
     `zvariant::Structure<'_>` implements `From<(T0, T1, T2, T3, T4, T5, T6, T7, T8)>`    and 8 others    required for `NetConfigMethodArgs` to implement `Into<zvariant::Structure<'_>>`    required for `zvariant::Value<'_>` to implement `From<NetConfigMethodArgs>`    1 redundant requirement hidden    required for `NetConfigMethodArgs` to implement `Into<zvariant::Value<'_>>` [E0277]
required by a bound introduced by this call [E0277]

I probably need to implement some traits to make a conversion form my structs to Value and viceversa.


Solution

  • I found a solution. It seems that the call_method was not expecting a zvariant::Value, but whatever type that could match some specific traits. zvariant::Structure<'_> was one of those types, so I only had to convert my structure to a zvariant::Structure<'_> by implementing the From trait:

    impl From<NotificationsMethodArgs> for zvariant::Structure<'_> {
        fn from(value: NotificationsMethodArgs) -> Self {
            let ret_value = zvariant::StructureBuilder::new()
            .add_field(value.app_name)
            .add_field(value.replaces_id)
            .add_field(value.app_icon)
            .add_field(value.summary)
            .add_field(value.body)
            .add_field(value.actions)
            .add_field(value.hints)
            .add_field(value.timeout).build().unwrap();
    
            ret_value
        }
    }
    

    Also, instead of having to deal with lifetimes, I changed the NotificationsMethodArgs to have a HashMap<String, OwnedValue> instead of HashMap<&str, &Value>

    #[derive(Debug, PartialEq, Deserialize, Serialize, Type)]
    struct NotificationsMethodArgs {
        app_name: String,
        replaces_id: u32,
        app_icon: String,
        summary: String,
        body: String,
        actions: Vec<String>,
        hints: HashMap<String, OwnedValue>,
        timeout: i32,
    }