Crate mqtt_typed_client

Source
Expand description

ยงMQTT Typed Client

ยง๐Ÿฆ€ MQTT Typed Client

A type-safe async MQTT client built on top of rumqttc

Automatic topic routing and subscription management with compile-time guarantees

CI Crates.io Documentation License: MIT OR Apache-2.0 MSRV

ยงโœจ Key Features

  • Type-safe topic patterns with named parameters and automatic parsing
  • Zero-cost abstractions via procedural macros with compile-time validation
  • IDE-friendly experience - full autocomplete for topics, parameters, and generated client methods
  • Automatic subscription management with intelligent routing and lifecycle handling
  • Built-in serialization support for 8+ formats (Bincode, JSON, MessagePack, etc.)
  • Efficient message routing with tree-based topic matching and internal caching
  • Smart defaults with full configurability when needed
  • Memory efficient design with proper resource management
  • Automatic reconnection and graceful shutdown

โš ๏ธ MSRV: Rust 1.85.1 (driven by default bincode serializer; can be lowered with alternative serializers)

ยง๐Ÿš€ Quick Start

Add to your Cargo.toml:

[dependencies]
mqtt-typed-client = "0.1.0"
use mqtt_typed_client::prelude::*;
use mqtt_typed_client_macros::mqtt_topic;
use serde::{Deserialize, Serialize};
use bincode::{Encode, Decode};

#[derive(Serialize, Deserialize, Encode, Decode, Debug)]
enum SensorStatus {
    Active,
    Inactive,
    Maintenance,
}

#[derive(Serialize, Deserialize, Encode, Decode, Debug)]
struct SensorReading {
    temperature: f64,
    status: SensorStatus,    // enum field
    location_note: String,   // string field for variety
}

// Define typed topic with automatic parameter extraction
#[mqtt_topic("sensors/{location}/{device_id}/data")]
struct SensorTopic {
    location: String,    // String parameter
    device_id: u32,      // Numeric parameter - automatic conversion!
    payload: SensorReading,
}

#[tokio::main]
async fn main() -> Result<()> {
    // Connect to MQTT broker
    let (client, connection) = MqttClient::<BincodeSerializer>::connect(
        "mqtt://broker.hivemq.com:1883"
    ).await?;

    // Get typed client for this specific topic - method generated by macro
    // Returns a typed client for publishing and subscribing to messages 
    // with automatic parameter handling for this topic pattern
    let topic_client = client.sensor_topic();
    
    // Subscribe to all matching topics: "sensors/+/+/data"
    // Returns typed subscriber that automatically extracts and converts
    // topic parameters into struct fields
    let mut subscriber = topic_client.subscribe().await?;
    
    let reading = SensorReading { 
        temperature: 22.5,
        status: SensorStatus::Active,
        location_note: "Kitchen sensor near window".to_string(),
    };
    
    // Publish with automatic type conversion to specific topic: "sensors/kitchen/42/data"
    // Parameters are automatically converted to strings and inserted into topic pattern
    topic_client.publish("kitchen", 42u32, &reading).await?;
    //                    ^^^^^^^^  ^^^^^ 
    //                    String    u32 -> automatically converts to "42" in topic
    
    // Receive with automatic parameter extraction and conversion
    if let Some(Ok(msg)) = subscriber.receive().await {
        println!("Device {} in location '{}' reported: temp={}ยฐC, status={:?}", 
            msg.device_id,  // u32 (converted from "42" in topic)
            msg.location,   // String (extracted from topic)
            msg.payload.temperature, msg.payload.status);
    }
    
    connection.shutdown().await?;
    Ok(())
}

ยง๐Ÿ“š Examples

See crate::examples - Complete usage examples with source code

  • 000_hello_world.rs - Basic publish/subscribe with macros
  • 001_ping_pong.rs - Multi-client communication
  • 002_configuration.rs - Advanced client configuration
  • 003_hello_world_lwt.rs - Last Will & Testament
  • 004_hello_world_tls.rs - TLS/SSL connections
  • 005_hello_world_serializers.rs - Custom serializers
  • 006_retain_and_clear.rs - Retained messages
  • 007_custom_patterns.rs - Custom topic patterns
  • 008_modular_example.rs - Modular application structure

Run examples:

cargo run --example 000_hello_world

ยง๐Ÿ“ฆ Serialization Support

Multiple serialization formats are supported via feature flags:

  • bincode - Binary serialization (default, most efficient)
  • json - JSON serialization (default, human-readable)
  • messagepack - MessagePack binary format
  • cbor - CBOR binary format
  • postcard - Embedded-friendly binary format
  • ron - Rusty Object Notation
  • flexbuffers - FlatBuffers FlexBuffers
  • protobuf - Protocol Buffers (requires generated types)

Enable additional serializers:

[dependencies]
mqtt-typed-client = { version = "0.1.0", features = ["messagepack", "cbor"] }

Custom serializers can be implemented by implementing the MessageSerializer trait.

ยง๐ŸŽฏ Topic Pattern Matching

Supports MQTT wildcard patterns with named parameters:

  • {param} - Named parameter (equivalent to + wildcard)
  • {param:#} - Multi-level named parameter (equivalent to # wildcard)
โ“˜
use mqtt_typed_client_macros::mqtt_topic;

// Traditional MQTT wildcards
#[mqtt_topic("home/+/temperature")]     // matches: home/kitchen/temperature
struct SimplePattern { payload: f64 }

// Named parameters (recommended)
#[mqtt_topic("home/{room}/temperature")] // matches: home/kitchen/temperature  
struct NamedPattern { 
    room: String,        // Automatically extracted: "kitchen"
    payload: f64 
}

// Multi-level parameters
#[mqtt_topic("logs/{service}/{path:#}")]  // matches: logs/api/v1/users/create
struct LogPattern {
    service: String,     // "api"
    path: String,        // "v1/users/create"  
    payload: String     // Changed from Data to String
}

ยง๐Ÿ”ง Advanced Usage: Low-Level API

For cases where you need direct control without macros:

use mqtt_typed_client::prelude::*;
use serde::{Deserialize, Serialize};
use bincode::{Encode, Decode};

#[derive(Serialize, Deserialize, Encode, Decode, Debug)]
struct SensorData {
    temperature: f64,
    humidity: f64,
}

#[tokio::main]
async fn main() -> Result<()> {
    let (client, connection) = MqttClient::<BincodeSerializer>::connect(
        "mqtt://broker.hivemq.com:1883"
    ).await?;

    // Direct topic operations
    let publisher = client.get_publisher::<SensorData>("sensors/temperature")?;
    let mut subscriber = client.subscribe::<SensorData>("sensors/+").await?;

    let data = SensorData { temperature: 23.5, humidity: 45.0 };
    publisher.publish(&data).await?;

    if let Some((topic, result)) = subscriber.receive().await {
        match result {
            Ok(sensor_data) => println!("Received from {}: {:?}", topic.topic_path(), sensor_data),
            Err(e) => eprintln!("Deserialization error: {:?}", e),
        }
    }

    connection.shutdown().await?;
    Ok(())
}

ยง๐Ÿ†š What mqtt-typed-client adds over rumqttc

Publishing:

โ“˜
// rumqttc - manual topic construction and serialization
let sensor_id = "sensor001";
let data = SensorData { temperature: 23.5 };
let topic = format!("sensors/{}/temperature", sensor_id);
let payload = serde_json::to_vec(&data)?;
client.publish(topic, QoS::AtLeastOnce, false, payload).await?;

// mqtt-typed-client - type-safe, automatic  
topic_client.publish(&sensor_id, &data).await?;

Subscribing with routing:

โ“˜
// rumqttc - manual pattern matching and dispatching
// while let Ok(event) = eventloop.poll().await {
//     if let Event::Incoming(Packet::Publish(publish)) = event {
//         if publish.topic.starts_with("sensors/") {
//             // Manual topic parsing, manual deserialization...
//         } else if publish.topic.starts_with("alerts/") {
//             // More manual parsing...
//         }
//     }
// }

// mqtt-typed-client - automatic routing to typed handlers
let mut sensor_sub = client.sensor_topic().subscribe().await?;
let mut alert_sub = client.alert_topic().subscribe().await?;

tokio::select! {
    msg = sensor_sub.receive() => { /* typed sensor data ready */ }
    msg = alert_sub.receive() => { /* typed alert data ready */ }
}

๐Ÿ“‹ For detailed comparison see: docs/COMPARISON_WITH_RUMQTTC.md

ยง๐Ÿ“„ License

This project is licensed under either of

at your option.

ยง๐Ÿค Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

See CONTRIBUTING.md for detailed guidelines.

ยง๐Ÿ“– API Reference

For detailed API documentation, visit docs.rs/mqtt-typed-client.

ยง๐Ÿ”— See Also

ยงAPI Reference

Key traits and modules:

ยงSee Also

Modulesยง

_macro_docs
Procedural Macros
advanced
Advanced types and utilities for complex use cases
client
MQTT client module
comparison
Detailed comparison with rumqttc
connection
MQTT connection management module
errors
Error types used throughout the library
examples
Complete usage examples with source code
info
Library metadata and version information
message_serializer
Message serialization traits and implementations.
prelude
Convenient imports for common use cases
routing
Message routing and subscription management module
structured
Structured MQTT subscribers with automatic topic parameter extraction
topic
Topic handling module

Structsยง

BincodeSerializer
Default serializer using bincode format.
ClientSettings
Client-level performance and behavior settings for the MQTT typed client.
JsonSerializer
JSON serializer using serde_json.
MqttClient
Type-safe MQTT client with automatic subscription management.
MqttClientConfig
Configuration for MQTT client creation
MqttConnection
MQTT connection handle for lifecycle management
MqttOptions
Options to configure the behaviour of MQTT connection
MqttPublisher
Typed MQTT publisher for a specific topic.
MqttSubscriber
Typed MQTT subscriber for topic patterns.
MqttTopicSubscriber
Structured MQTT subscriber with automatic topic parameter extraction.
SubscriptionBuilder
Immutable builder for configuring MQTT subscriptions
SubscriptionConfig
TopicPatternPath
Parsed MQTT topic pattern with wildcard support
TypedLastWill
Represents a Last Will and Testament (LWT) message for MQTT clients.

Enumsยง

CacheStrategy
MessageConversionError
Errors that occur during message conversion from MQTT topics.
MqttClientError
Errors that can occur in MQTT client operations
QoS
Quality of service
TopicError
Comprehensive error type for all topic-related operations
TopicPatternError
Error types for topic pattern parsing

Constantsยง

VERSION

Traitsยง

FromMqttMessage
Trait for converting MQTT messages into structured types.
MessageSerializer
Trait for serializing and deserializing MQTT message payloads.

Functionsยง

extract_topic_parameter
Extract and parse a topic parameter by wildcard index

Type Aliasesยง

Result
Result type alias for operations that may fail with MqttClientError

Attribute Macrosยง

mqtt_topic
Generate a typed MQTT subscriber and/or publisher from a struct and topic pattern