Skip to main content

Crate mqtt_typed_client

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.2.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 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
  • 102_multi_serializer_macro.rs - Per-topic custom serializers

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.2.0", features = ["messagepack", "cbor"] }

Custom serializers can be implemented by implementing the MessageSerializer trait.

§Per-Topic Serializer Override

By default every topic uses the client’s serializer. You can override it for a specific topic type — handy for legacy formats or gradual migrations:

use mqtt_typed_client_macros::mqtt_topic;

// This topic always uses JSON, regardless of the client's default serializer.
#[mqtt_topic("legacy/devices/{id}/status", serializer = JsonSerializer)]
struct LegacyStatus {
    id: u32,
    payload: DeviceStatus,
}

Limitations:

  • Using a custom serializer disables the generated TypedClient extension for that topic (typed clients require a generic serializer parameter, while a custom serializer is a concrete type).
  • Only a simple type path is accepted. For a generic serializer, declare a type alias first: type MySer = MySerializer<Foo>; then use serializer = MySer.

§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
}

§TLS and Transport

Transport security and extras are opt-in via feature flags (all forwarded to rumqttc):

FeatureEffect
rumqttc-use-rustls (default)TLS via rustls with the aws-lc-rs provider
rumqttc-use-rustls-no-providerrustls without a bundled crypto provider — bring your own (e.g. ring) and avoid the aws-lc build
rumqttc-use-native-tlsTLS via the platform’s native-tls
rumqttc-websocketMQTT over WebSocket
rumqttc-proxyConnect through an HTTP/HTTPS proxy

For custom TLS setups you can build the rustls config yourself. The crate re-exports rumqttc’s rustls and tokio_rustls (version-matched, so you don’t add a separate rustls dependency that could drift out of sync):

use mqtt_typed_client::rustls::{ClientConfig, RootCertStore};

let mut root_store = RootCertStore::empty();
// Add your trusted roots to `root_store` here (e.g. parsed from a PEM file).

ClientConfig::builder()
    .with_root_certificates(root_store)
    .with_no_client_auth()

See examples/004_hello_world_tls.rs for a complete TLS example, including loading a CA certificate from a PEM file.

§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 a detailed comparison see: docs/COMPARISON_WITH_RUMQTTC.md

§Alternatives

  • rumqttc — the async MQTT client this crate builds on. Use it directly when you want full manual control over topics, serialization, and the event loop.
  • paho-mqtt — Rust bindings to the Eclipse Paho C client; a fit when you need that mature C library or its feature set.
  • ntex-mqtt — MQTT client and server built on the ntex framework; worth a look if you’re already in that ecosystem or need a broker.

Reach for mqtt-typed-client when you want typed topic routing and automatic (de)serialization on top of rumqttc, without hand-writing the dispatch layer.

§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

Re-exports§

pub use rumqttc::tokio_rustls;
pub use rumqttc::tokio_rustls::rustls;

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 - re-exported from mqtt-topic-engine

Structs§

BincodeSerializer
Default serializer using bincode format.
CborSerializer
CBOR serializer using ciborium.
ClientSettings
Client-level performance and behavior settings for the MQTT typed client.
FlexbuffersSerializer
Flexbuffers serializer using flexbuffers crate.
JsonSerializer
JSON serializer using serde_json.
MessagePackSerializer
MessagePack serializer using rmp-serde.
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.
PostcardSerializer
Postcard serializer using postcard crate.
ProtobufSerializer
Protocol Buffers serializer using prost crate.
RonSerializer
RON (Rusty Object Notation) serializer using ron crate.
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
Strategy for caching topic matching results
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
Transport
Transport methods. Defaults to TCP.

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