An open API service indexing awesome lists of open source software.

https://github.com/holovskyi/mqtt-typed-client

Type-safe async MQTT client with automatic topic routing
https://github.com/holovskyi/mqtt-typed-client

async client iot mqtt mqtt-client rust type-safety typed

Last synced: 3 months ago
JSON representation

Type-safe async MQTT client with automatic topic routing

Awesome Lists containing this project

README

          

# 🦀 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](https://github.com/holovskyi/mqtt-typed-client/workflows/CI/badge.svg)](https://github.com/holovskyi/mqtt-typed-client/actions)
[![Crates.io](https://img.shields.io/crates/v/mqtt-typed-client.svg)](https://crates.io/crates/mqtt-typed-client)
[![Documentation](https://docs.rs/mqtt-typed-client/badge.svg)](https://docs.rs/mqtt-typed-client)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-blue.svg)](LICENSE-MIT)
[![MSRV](https://img.shields.io/badge/MSRV-1.85.1-blue.svg)](https://blog.rust-lang.org/2025/01/09/Rust-1.85.0.html)

**Like this project? [⭐ Star it on GitHub!](https://github.com/holovskyi/mqtt-typed-client)**

## ✨ 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`:
```toml
[dependencies]
mqtt-typed-client = "0.1.0"
```

```rust,no_run
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::::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(())
}
```

**Like this project? [⭐ Star it on GitHub!](https://github.com/holovskyi/mqtt-typed-client)**

## 📚 Examples

See [examples/](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:
```bash
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:
```toml
[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)

```rust,ignore
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:

```rust,no_run
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::::connect(
"mqtt://broker.hivemq.com:1883"
).await?;

// Direct topic operations
let publisher = client.get_publisher::("sensors/temperature")?;
let mut subscriber = client.subscribe::("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:**
```rust,ignore
// 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:**
```rust,ignore
// 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](docs/COMPARISON_WITH_RUMQTTC.md)

## 📄 License

This project is licensed under either of

* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or )
* MIT license ([LICENSE-MIT](LICENSE-MIT) or )

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](CONTRIBUTING.md) for detailed guidelines.

## 📖 API Reference

For detailed API documentation, visit [docs.rs/mqtt-typed-client](https://docs.rs/mqtt-typed-client).

## 🔗 See Also

- [rumqttc](https://github.com/bytebeamio/rumqtt) - The underlying MQTT client library
- [MQTT Protocol Specification](https://mqtt.org/) - Official MQTT documentation
- [Rust Async Book](https://rust-lang.github.io/async-book/) - Guide to async Rust programming