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
- Host: GitHub
- URL: https://github.com/holovskyi/mqtt-typed-client
- Owner: holovskyi
- License: other
- Created: 2025-07-26T17:47:20.000Z (11 months ago)
- Default Branch: main
- Last Pushed: 2025-07-27T19:00:13.000Z (11 months ago)
- Last Synced: 2025-08-28T22:56:47.485Z (10 months ago)
- Topics: async, client, iot, mqtt, mqtt-client, rust, type-safety, typed
- Language: Rust
- Homepage:
- Size: 438 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE-APACHE
- Roadmap: docs/ROADMAP.md
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**
[](https://github.com/holovskyi/mqtt-typed-client/actions)
[](https://crates.io/crates/mqtt-typed-client)
[](https://docs.rs/mqtt-typed-client)
[](LICENSE-MIT)
[](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