agentlink-core 0.1.0

AgentLink SDK Core - Platform agnostic core library
Documentation
//! MQTT Client Abstraction
//!
//! This module defines the MQTT client trait that must be implemented
//! by platform-specific backends (native rumqttc, wasm websocket, etc.)

use async_trait::async_trait;

use crate::error::SdkResult;

/// MQTT connection state
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MqttConnectionState {
    Disconnected,
    Connecting,
    Connected,
    Reconnecting,
    Disconnecting,
    Failed,
}

impl MqttConnectionState {
    pub fn can_connect(&self) -> bool {
        matches!(self, Self::Disconnected | Self::Failed)
    }

    pub fn is_connected(&self) -> bool {
        matches!(self, Self::Connected)
    }
}

/// MQTT QoS level
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MqttQoS {
    AtMostOnce,   // QoS 0
    AtLeastOnce,  // QoS 1
    ExactlyOnce,  // QoS 2
}

/// MQTT message
#[derive(Debug, Clone)]
pub struct MqttMessage {
    pub topic: String,
    pub payload: Vec<u8>,
    pub qos: MqttQoS,
}

impl MqttMessage {
    pub fn new(topic: impl Into<String>, payload: Vec<u8>) -> Self {
        Self {
            topic: topic.into(),
            payload,
            qos: MqttQoS::AtLeastOnce,
        }
    }

    pub fn with_qos(mut self, qos: MqttQoS) -> Self {
        self.qos = qos;
        self
    }

    pub fn payload_string(&self) -> Result<String, std::string::FromUtf8Error> {
        String::from_utf8(self.payload.clone())
    }
}

/// MQTT event
#[derive(Debug, Clone)]
pub enum MqttEvent {
    Connected,
    Disconnected,
    Reconnecting { attempt: u32, delay_ms: u64 },
    Reconnected,
    ConnectionFailed { error: String },
    MessageReceived(MqttMessage),
    Subscribed { topic: String },
    Unsubscribed { topic: String },
    Published { topic: String },
    Error { error: String },
}

/// MQTT client configuration
#[derive(Debug, Clone)]
pub struct MqttConfig {
    pub broker_url: String,
    pub client_id: String,
    pub username: Option<String>,
    pub password: Option<String>,
    pub keep_alive_secs: u64,
    pub clean_session: bool,
}

impl Default for MqttConfig {
    fn default() -> Self {
        Self {
            broker_url: "mqtts://localhost:8883".to_string(),
            client_id: String::new(),
            username: None,
            password: None,
            keep_alive_secs: 30,
            clean_session: true,
        }
    }
}

/// MQTT client trait
///
/// Platform-specific implementations must implement this trait.
#[async_trait]
#[cfg(not(target_arch = "wasm32"))]
pub trait MqttClient: Send + Sync {
    /// Connect to MQTT broker
    async fn connect(&self, config: MqttConfig) -> SdkResult<()>;

    /// Disconnect from broker
    async fn disconnect(&self) -> SdkResult<()>;

    /// Subscribe to a topic
    async fn subscribe(&self, topic: &str, qos: MqttQoS) -> SdkResult<()>;

    /// Unsubscribe from a topic
    async fn unsubscribe(&self, topic: &str) -> SdkResult<()>;

    /// Publish a message
    async fn publish(&self, message: MqttMessage) -> SdkResult<()>;

    /// Get current connection state
    fn connection_state(&self) -> MqttConnectionState;

    /// Check if connected
    fn is_connected(&self) -> bool {
        self.connection_state().is_connected()
    }
}

/// MQTT client trait (WASM version)
///
/// Platform-specific implementations must implement this trait.
#[async_trait(?Send)]
#[cfg(target_arch = "wasm32")]
pub trait MqttClient {
    /// Connect to MQTT broker
    async fn connect(&self, config: MqttConfig) -> SdkResult<()>;

    /// Disconnect from broker
    async fn disconnect(&self) -> SdkResult<()>;

    /// Subscribe to a topic
    async fn subscribe(&self, topic: &str, qos: MqttQoS) -> SdkResult<()>;

    /// Unsubscribe from a topic
    async fn unsubscribe(&self, topic: &str) -> SdkResult<()>;

    /// Publish a message
    async fn publish(&self, message: MqttMessage) -> SdkResult<()>;

    /// Get current connection state
    fn connection_state(&self) -> MqttConnectionState;

    /// Check if connected
    fn is_connected(&self) -> bool {
        self.connection_state().is_connected()
    }
}

/// Trait for MQTT clients that support event callbacks
#[cfg(not(target_arch = "wasm32"))]
pub trait MqttClientWithEvents: MqttClient {
    /// Set event callback
    ///
    /// The callback will be called when MQTT events occur.
    fn on_event<F>(&self, callback: F)
    where
        F: Fn(MqttEvent) + Send + Sync + 'static;
}

/// Trait for MQTT clients that support event callbacks (WASM version)
#[cfg(target_arch = "wasm32")]
pub trait MqttClientWithEvents: MqttClient {
    /// Set event callback
    ///
    /// The callback will be called when MQTT events occur.
    fn on_event<F>(&self, callback: F)
    where
        F: Fn(MqttEvent) + 'static;
}

/// MQTT client factory trait
#[cfg(not(target_arch = "wasm32"))]
pub trait MqttClientFactory: Send + Sync {
    fn create_client(&self) -> Box<dyn MqttClient>;
}

/// MQTT client factory trait (WASM version)
#[cfg(target_arch = "wasm32")]
pub trait MqttClientFactory {
    fn create_client(&self) -> Box<dyn MqttClient>;
}