eclipse-paho / paho.mqtt.rust

paho.mqtt.rust
Other
529 stars 102 forks source link

Feature request: Implement serde::Deserialize and serde::Serialize for CreateOptions and Connect options #221

Open Altair-Bueno opened 8 months ago

Altair-Bueno commented 8 months ago

As the title says, both of these structures could benefit from Serialization/De-serialization capabilities. This can be useful on config files, for example.

fpagliughi commented 8 months ago

That sounds like a great idea. I think the best thing would be to put them behind a Cargo build feature like "serde".

The only problem is that the internals of those structs contain a lot of FFI components which may not serialize nicely. But I was rethinking how I implemented all of the options and figure it would be better to make them completely "Rusty" and then create the FFI only when needed. That would also be a good first step as we slowly move the library to 100% Rust.

Altair-Bueno commented 8 months ago

Yes indeed, se/de should be gated behind a feature flag.


My current workaround looks as it follows (only for deserialization), just in case someone has the same needs as I do and stumbles on this issue.

#[derive(Debug, Default)]
pub struct MqttConfig {
    pub create_options: CreateOptions,
    pub connections: Vec<ConnectOptions>,
    pub buffer_size: Option<usize>,
}

#[derive(Debug, Default, serde::Deserialize)]
pub struct RawMqttConfig {
    create_options: RawCreateOptions,
    connections: Vec<RawConnectOptions>,
    buffer_size: Option<usize>,
}

#[derive(Debug, Default, serde::Deserialize)]
pub struct RawCreateOptions {
    #[serde(default)]
    version: Option<u32>,
    #[serde(default)]
    client_id: Option<String>,
    #[serde(default)]
    persistence: Option<PathBuf>,
    #[serde(default)]
    max_buffered_messages: Option<i32>,
    #[serde(default)]
    send_while_disconnected: Option<bool>,
    #[serde(default)]
    allow_disconnected_send_at_anytime: Option<bool>,
    #[serde(default)]
    delete_oldest_messages: Option<bool>,
    #[serde(default)]
    restore_messages: Option<bool>,
    #[serde(default)]
    persist_qos0: Option<bool>,
}
#[derive(Debug, Default, serde::Deserialize)]
pub struct RawConnectOptions {
    server_uris: Vec<String>,
}

impl<'de> Deserialize<'de> for MqttConfig {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let RawMqttConfig {
            create_options: raw_create_options,
            connections: raw_connections,
            buffer_size,
        } = RawMqttConfig::deserialize(deserializer)?;

        let mut create_options = CreateOptionsBuilder::default();
        if let Some(version) = raw_create_options.version {
            create_options = create_options.mqtt_version(version);
        }
        if let Some(client_id) = raw_create_options.client_id {
            create_options = create_options.client_id(client_id);
        }
        if let Some(persistence) = raw_create_options.persistence {
            create_options = create_options.persistence(PersistenceType::FilePath(persistence));
        }
        if let Some(max_buffered_messages) = raw_create_options.max_buffered_messages {
            create_options = create_options.max_buffered_messages(max_buffered_messages);
        }
        if let Some(send_while_disconnected) = raw_create_options.send_while_disconnected {
            create_options = create_options.send_while_disconnected(send_while_disconnected);
        }
        if let Some(allow_disconnected_send_at_anytime) =
            raw_create_options.allow_disconnected_send_at_anytime
        {
            create_options = create_options
                .allow_disconnected_send_at_anytime(allow_disconnected_send_at_anytime);
        }
        if let Some(delete_oldest_messages) = raw_create_options.delete_oldest_messages {
            create_options = create_options.delete_oldest_messages(delete_oldest_messages);
        }
        if let Some(restore_messages) = raw_create_options.restore_messages {
            create_options = create_options.restore_messages(restore_messages);
        }
        if let Some(persist_qos0) = raw_create_options.persist_qos0 {
            create_options = create_options.persist_qos0(persist_qos0);
        }
        let create_options = create_options.finalize();

        let connections = raw_connections
            .into_iter()
            .map(|raw_connection| {
                let mut connection = ConnectOptionsBuilder::default();
                connection.server_uris(&raw_connection.server_uris);

                connection.finalize()
            })
            .collect();

        Ok(MqttConfig {
            create_options,
            connections,
            buffer_size,
        })
    }
}

Definitely ugly and error prone but it gets the job done.