Skip to content

Commit b02ec70

Browse files
lxsaahclaude
andauthored
refactor(connectors)!: MQTT knobs out of core builders; prune ConnectorConfig (034 Phase 2, #134) (#139)
* refactor(core)!: panic-free builder validation — build() collects all configuration errors (#133) One failure model instead of two: builder methods never panic on user mistakes. TypedRecord setters, the connector-link finish() methods, and configure() record a ConfigError (skipping the conflicting registration) and build() returns a single DbError::InvalidConfiguration carrying every finding — record key and connector URL included — so one run surfaces every mistake. The spawn-time factory panics (missing buffer, record lookup) become build()-time checks; .buffer() after .link_to()/.link_from() is now legal. Duplicate keys and dependency-graph findings fold into the same collected report. Remaining panics on the builder path are internal invariants worded 'this is a bug in aimdb-core'. AnyRecord gains has_buffer() and drain_config_errors(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(connectors)!: move MQTT knobs out of core builders; prune ConnectorConfig (#134) Core knows schemes and key/value options, never protocol semantics: with_qos/with_retain are deleted from the generic link builders and now live in aimdb-mqtt-connector as MqttLinkExt (qos, outbound + inbound) and MqttOutboundLinkExt (retain, publish-side only) — pushing the same config keys the MQTT clients have always read from protocol_options, so wire behavior is unchanged. with_timeout_ms stays with protocol-neutral docs; with_config(key, value) is the generic spine. ConnectorConfig drops its never-read typed qos/retain fields and the speculative Kafka/HTTP/shmem interpretation docs; it keeps timeout_ms + protocol_options. Outbound/InboundConnectorBuilder are re-exported from core's root for the extension-trait impls. The tokio MQTT demo exercises the new traits explicitly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 576efb5 commit b02ec70

10 files changed

Lines changed: 220 additions & 88 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4444

4545
### Changed (breaking)
4646

47+
- **Design 034 Phase 2 — MQTT knobs move out of core; `ConnectorConfig` pruned (Issue #134, [review doc §3.6](docs/design/034-technical-debt-review.md)).** Core's generic link builders drop `with_qos`/`with_retain` (`with_timeout_ms` stays, de-MQTT'd); the knobs now live in `aimdb-mqtt-connector` as the `MqttLinkExt` (qos, outbound + inbound) and `MqttOutboundLinkExt` (retain, publish-side only) extension traits, pushing the **same** `("qos", …)`/`("retain", …)` option keys the MQTT clients have always read — wire behavior unchanged; importing the trait makes the MQTT intent explicit at the call site (generic escape hatch: `with_config(key, value)`). `ConnectorConfig` loses its never-read typed `qos`/`retain` fields and the speculative Kafka/HTTP/shmem interpretation docs — it keeps `timeout_ms` + `protocol_options`, and core now documents no protocol that lacks an in-tree connector. ([aimdb-core](aimdb-core/CHANGELOG.md), [aimdb-mqtt-connector](aimdb-mqtt-connector/CHANGELOG.md))
48+
4749
- **Design 034 Phase 2 — panic-free builder validation: `build()` reports every configuration mistake at once (Issue #133, [review doc §3.4](docs/design/034-technical-debt-review.md)).** Builder methods never panic on user mistakes anymore. Conflicting `.source()`/`.transform()`/`.link_from()` registrations, missing serializers/deserializers, invalid connector URLs, unregistered schemes, and key-reused-with-different-type are *recorded* (the conflicting registration is skipped) and `build()` returns one `DbError::InvalidConfiguration { errors: Vec<ConfigError> }` carrying **all** findings — each with the record key and, where applicable, the connector URL. The worst panic — "requires a buffer" firing at spawn time inside a connector factory closure — is now a build()-time check, which also makes `.buffer()` after `.link_to()`/`.link_from()` legal (order-independent). Duplicate keys and dependency-graph cycles fold into the same collected report (previously distinct `DuplicateRecordKey`/`CyclicDependency` returns from `build()`). Remaining `panic!`/`expect`s in the builder path are internal invariants and say "this is a bug in aimdb-core". ([aimdb-core](aimdb-core/CHANGELOG.md))
4850

4951
- **Design 034 Phase 2 — registrar lifetime fix + de-erased builder internals (Issue #130, [review doc](docs/design/034-technical-debt-review.md)).** `RecordRegistrar`'s fluent methods now take fresh borrows (`&mut self -> &mut Self`) instead of borrowing the registrar for its entire lifetime — a `configure` closure can finally use separate statements (`reg.source_raw(…); reg.tap_raw(…);`) and reuse the registrar after a chain. `configure`'s closure bound drops its HRTB; `OutboundConnectorBuilder`/`InboundConnectorBuilder` gain a second lifetime parameter (`<'r, 'a, T, R>`); `RecordT::register` and the adapter/persistence extension traits follow. Internally, `AimDbBuilder` stores its spawn/start functions typed (`SpawnFnType<R>`/`StartFnType<R>`) instead of `Box<dyn Any>` — the panicking downcasts in `build()` are gone, and `AimDb<R>`'s struct-level bound moved to its impls. Closure-based user code compiles unchanged. ([aimdb-core](aimdb-core/CHANGELOG.md), [aimdb-embassy-adapter](aimdb-embassy-adapter/CHANGELOG.md), [aimdb-persistence](aimdb-persistence/CHANGELOG.md))

aimdb-core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4343

4444
### Changed (breaking)
4545

46+
- **Phase 2 — MQTT knobs deleted from the generic link builders; `ConnectorConfig` pruned (Issue #134, [design doc §3.6](../docs/design/034-technical-debt-review.md)).** `OutboundConnectorBuilder::with_qos`/`with_retain` and `InboundConnectorBuilder::with_qos` are gone — use `aimdb-mqtt-connector`'s `MqttLinkExt`/`MqttOutboundLinkExt` (same option keys, wire-identical) or the generic `with_config(key, value)`. `with_timeout_ms` survives with protocol-neutral docs. `ConnectorConfig` drops its typed `qos: u8`/`retain: bool` fields (verified unread by every connector — MQTT reads `protocol_options`) and the Kafka/HTTP/shmem interpretation docs; it keeps `timeout_ms` + `protocol_options`. `OutboundConnectorBuilder`/`InboundConnectorBuilder` are now re-exported from the crate root (for the extension-trait impls).
47+
4648
- **Phase 2 — panic-free builder validation; `build()` collects every configuration mistake (Issue #133, [design doc §3.4](../docs/design/034-technical-debt-review.md)).** One failure model instead of two: builder methods stay infallible and never panic on user mistakes; `build()` performs all validation and returns one `DbError::InvalidConfiguration { errors: Vec<ConfigError> }` (new variant + new public `ConfigError { record_key, url, message }` type, error code `0x4003`) carrying **every** finding from the run. Specifics:
4749
- `TypedRecord::set_producer`/`set_transform`/`add_inbound_connector` mutual-exclusion violations and duplicate producers/transforms: recorded, conflicting registration skipped (observable: `has_producer()` stays `false` after a conflicting `.source()`).
4850
- `OutboundConnectorBuilder::finish()`/`InboundConnectorBuilder::finish()`: invalid URL, missing serializer/deserializer, unregistered scheme, and the transform/source conflicts are recorded with record key + URL; the link is not registered and the registrar is returned as usual.

aimdb-core/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,10 @@ pub use builder::OutboundRoute;
8181
pub use builder::{AimDb, AimDbBuilder};
8282
pub use connector::ConnectorBuilder;
8383
pub use transport::{Connector, ConnectorConfig, PublishError};
84-
pub use typed_api::{Consumer, Producer, RecordRegistrar, RecordT, StageKind};
84+
pub use typed_api::{
85+
Consumer, InboundConnectorBuilder, OutboundConnectorBuilder, Producer, RecordRegistrar,
86+
RecordT, StageKind,
87+
};
8588
pub use typed_record::{AnyRecord, AnyRecordExt, TypedRecord};
8689

8790
// JSON codec (feature `json-serialize`, no_std + alloc compatible)

aimdb-core/src/transport.rs

Lines changed: 31 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,33 @@
1-
//! Transport connector traits for MQTT, Kafka, HTTP, shmem, and other protocols
1+
//! Transport connector traits for protocol-agnostic publishing
22
//!
33
//! Provides a generic `Connector` trait that enables scheme-based routing
44
//! to different transport protocols. Each connector manages a single connection
5-
//! to a specific endpoint (e.g., one MQTT broker, one shared memory segment, etc.).
5+
//! to a specific endpoint (e.g., one MQTT broker).
66
//!
77
//! # Design Philosophy
88
//!
9-
//! - **Scheme-based routing**: URL scheme (mqtt://, shmem://, kafka://) determines which connector handles requests
9+
//! - **Scheme-based routing**: the URL scheme (e.g. `mqtt://`, `knx://`) determines which connector handles requests
1010
//! - **Single endpoint per connector**: Each connector connects to ONE broker/resource
1111
//! - **Multi-transport publishing**: Same data can be published to multiple protocols
12-
//! - **Protocol-agnostic core**: Core doesn't know about MQTT, Kafka, etc. - just routes by scheme
12+
//! - **Protocol-agnostic core**: Core knows schemes and key/value options, never protocol semantics
1313
1414
use alloc::{boxed::Box, string::String, vec::Vec};
1515
use core::future::Future;
1616
use core::pin::Pin;
1717

1818
/// Protocol-agnostic connector configuration
1919
///
20-
/// Provides common configuration options that apply across multiple protocols.
21-
/// Each protocol interprets these fields according to its semantics.
22-
///
23-
/// # Protocol Interpretation
24-
///
25-
/// - **MQTT**: qos=QoS level, retain=retain flag, timeout_ms=publish timeout
26-
/// - **Kafka**: qos=acks setting (0=none, 1=leader, 2=all), timeout_ms=send timeout
27-
/// - **HTTP**: qos=retry count, timeout_ms=request timeout
28-
/// - **Shmem**: qos=priority, retain=pin in memory
20+
/// Carries the route's key/value options to [`Connector::publish`]. Only the
21+
/// genuinely protocol-agnostic `timeout_ms` is a typed field; every
22+
/// protocol-specific knob (e.g. MQTT's `qos`/`retain`) travels in
23+
/// [`protocol_options`](ConnectorConfig::protocol_options) and is interpreted
24+
/// by the connector with its own defaults. Connector crates expose typed
25+
/// setters as extension traits over the link builders (e.g. the MQTT
26+
/// connector's `MqttLinkExt`).
2927
#[derive(Debug, Clone)]
3028
pub struct ConnectorConfig {
31-
/// Quality of Service / reliability level (0, 1, or 2)
32-
pub qos: u8,
33-
34-
/// Whether to retain/persist the message
35-
pub retain: bool,
36-
37-
/// Optional timeout in milliseconds
29+
/// Optional timeout in milliseconds for the publish/operation, as
30+
/// interpreted by the connector
3831
pub timeout_ms: Option<u32>,
3932

4033
/// Protocol-specific options as key-value pairs
@@ -45,8 +38,6 @@ pub struct ConnectorConfig {
4538
impl Default for ConnectorConfig {
4639
fn default() -> Self {
4740
Self {
48-
qos: 0,
49-
retain: false,
5041
timeout_ms: Some(5000),
5142
protocol_options: Vec::new(),
5243
}
@@ -60,16 +51,10 @@ impl ConnectorConfig {
6051
/// per-route configuration through to [`Connector::publish`] without changing
6152
/// the `publish` signature.
6253
///
63-
/// Only the protocol-agnostic `timeout_ms` is lifted into a typed field. The
64-
/// `qos`/`retain` *meaning* differs per protocol (an MQTT QoS level vs. a
65-
/// Kafka `acks` setting vs. an HTTP retry count — see the type docs), and a
66-
/// `u8`/`bool` field cannot represent "unspecified", so these — and every
67-
/// other key — are passed through verbatim in [`protocol_options`] for the
68-
/// connector to interpret with its own defaults. The typed `qos`/`retain`
69-
/// fields therefore keep their [`Default`] values here; they remain available
70-
/// for callers that construct a [`ConnectorConfig`] directly.
71-
///
72-
/// [`protocol_options`]: ConnectorConfig::protocol_options
54+
/// Only the protocol-agnostic `timeout_ms` is lifted into the typed field;
55+
/// every other key is passed through verbatim in
56+
/// [`protocol_options`](ConnectorConfig::protocol_options) for the
57+
/// connector to interpret with its own defaults.
7358
pub fn from_query(query: &[(String, String)]) -> ConnectorConfig {
7459
let mut cfg = ConnectorConfig::default();
7560
for (k, v) in query {
@@ -139,12 +124,8 @@ impl std::error::Error for PublishError {}
139124

140125
/// Generic transport connector trait for protocol-agnostic publishing
141126
///
142-
/// This trait enables multi-protocol publishing via scheme-based routing:
143-
/// - `mqtt://topic` → MQTT broker
144-
/// - `shmem://segment` → Shared memory
145-
/// - `kafka://topic` → Kafka cluster
146-
/// - `http://endpoint` → HTTP POST
147-
/// - `dds://topic` → DDS topic
127+
/// This trait enables multi-protocol publishing via scheme-based routing
128+
/// (e.g. `mqtt://topic` → MQTT broker, `knx://1/0/6` → KNX group address).
148129
///
149130
/// Each connector manages ONE connection/endpoint. For multiple brokers/endpoints,
150131
/// create multiple connectors and register them with different schemes.
@@ -159,30 +140,22 @@ impl std::error::Error for PublishError {}
159140
/// config: &ConnectorConfig,
160141
/// payload: &[u8],
161142
/// ) -> Pin<Box<dyn Future<Output = Result<(), PublishError>> + Send + '_>> {
143+
/// // Protocol knobs come from the route's key/value options,
144+
/// // with connector-chosen defaults.
145+
/// let qos = config
146+
/// .protocol_options
147+
/// .iter()
148+
/// .find(|(k, _)| k == "qos")
149+
/// .and_then(|(_, v)| v.parse::<u8>().ok())
150+
/// .unwrap_or(1);
162151
/// Box::pin(async move {
163-
/// self.client.publish(destination, config.qos, config.retain, payload).await
152+
/// self.client.publish(destination, qos, payload).await
164153
/// .map_err(|_| PublishError::ConnectionFailed)
165154
/// })
166155
/// }
167156
/// }
168157
/// ```
169158
///
170-
/// # Usage
171-
///
172-
/// ```rust,ignore
173-
/// let mqtt_connector = MqttConnector::new("mqtt://broker.local:1883").await?;
174-
///
175-
/// let db = AimDbBuilder::new()
176-
/// .runtime(runtime)
177-
/// .with_connector("mqtt", Arc::new(mqtt_connector))
178-
/// .configure::<Temperature>(|reg| {
179-
/// reg.link_to("mqtt://sensors/temp")
180-
/// .with_qos(1)
181-
/// .finish()
182-
/// })
183-
/// .build()?;
184-
/// ```
185-
///
186159
/// # Thread Safety
187160
///
188161
/// Requires Send + Sync for Tokio compatibility. For Embassy (single-threaded),
@@ -191,12 +164,9 @@ pub trait Connector: Send + Sync {
191164
/// Publish data to a protocol-specific destination
192165
///
193166
/// # Arguments
194-
/// * `destination` - Protocol-specific path (no broker/host info):
195-
/// - MQTT: "sensors/temperature"
196-
/// - Shmem: "temp_readings"
197-
/// - Kafka: "production/events"
198-
/// - HTTP: "api/v1/sensors"
199-
/// * `config` - Publishing configuration (QoS, retain, timeout, protocol options)
167+
/// * `destination` - Protocol-specific path, no broker/host info
168+
/// (e.g. an MQTT topic like "sensors/temperature")
169+
/// * `config` - Publishing configuration (timeout + protocol options)
200170
/// * `payload` - Message payload as byte slice
201171
///
202172
/// # Returns
@@ -239,8 +209,6 @@ mod tests {
239209
#[test]
240210
fn test_connector_config_default() {
241211
let config = ConnectorConfig::default();
242-
assert_eq!(config.qos, 0);
243-
assert!(!config.retain);
244212
assert_eq!(config.timeout_ms, Some(5000));
245213
assert_eq!(config.protocol_options.len(), 0);
246214
}

aimdb-core/src/typed_api.rs

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -703,19 +703,13 @@ where
703703
self
704704
}
705705

706-
/// Sets the MQTT Quality of Service level
707-
pub fn with_qos(mut self, qos: u8) -> Self {
708-
self.config.push(("qos".to_string(), qos.to_string()));
709-
self
710-
}
711-
712-
/// Sets the MQTT retain flag
713-
pub fn with_retain(mut self, retain: bool) -> Self {
714-
self.config.push(("retain".to_string(), retain.to_string()));
715-
self
716-
}
717-
718-
/// Sets the publish timeout in milliseconds
706+
/// Sets the operation timeout in milliseconds (the connector interprets
707+
/// it; passed as the `timeout_ms` option — see
708+
/// `ConnectorConfig::from_query`)
709+
///
710+
/// Protocol-specific knobs (e.g. MQTT QoS/retain) are provided by the
711+
/// connector crates as extension traits over this builder, or generically
712+
/// via [`with_config`](Self::with_config).
719713
pub fn with_timeout_ms(mut self, timeout_ms: u32) -> Self {
720714
self.config
721715
.push(("timeout_ms".to_string(), timeout_ms.to_string()));
@@ -992,13 +986,13 @@ where
992986
self
993987
}
994988

995-
/// Sets the MQTT Quality of Service level
996-
pub fn with_qos(mut self, qos: u8) -> Self {
997-
self.config.push(("qos".to_string(), qos.to_string()));
998-
self
999-
}
1000-
1001-
/// Sets the publish timeout in milliseconds
989+
/// Sets the operation timeout in milliseconds (the connector interprets
990+
/// it; passed as the `timeout_ms` option — see
991+
/// `ConnectorConfig::from_query`)
992+
///
993+
/// Protocol-specific knobs (e.g. MQTT subscribe QoS) are provided by the
994+
/// connector crates as extension traits over this builder, or generically
995+
/// via [`with_config`](Self::with_config).
1002996
pub fn with_timeout_ms(mut self, timeout_ms: u32) -> Self {
1003997
self.config
1004998
.push(("timeout_ms".to_string(), timeout_ms.to_string()));

aimdb-mqtt-connector/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- **`MqttLinkExt` / `MqttOutboundLinkExt` — the MQTT knobs, now where the protocol lives (Issue #134, design 034 §3.6).** New `link_ext` module (compiled on every feature leg, `alloc`-only) with extension traits over core's generic link builders: `MqttLinkExt::with_qos(u8)` on outbound *and* inbound links (publish / subscribe QoS), and `MqttOutboundLinkExt::with_retain(bool)` on outbound links only (retain is a publish-side flag). They push the exact `("qos", …)` / `("retain", …)` option keys both clients have always read from `protocol_options` — wire behavior identical to the deleted core methods; only an extra `use aimdb_mqtt_connector::{MqttLinkExt, MqttOutboundLinkExt};` is needed. The crate now declares `extern crate alloc` unconditionally.
13+
1014
### Changed
1115

1216
- **Connector-build errors carry their message on `no_std` too (Issue #129).** With `DbError` unified on `alloc::String`, the dual `#[cfg]` error-construction branches in both clients collapse to one `DbError::runtime_error(...)` expression; the Embassy client's "Failed to build MQTT connector" detail is no longer dropped on embedded targets. No API change.

aimdb-mqtt-connector/src/lib.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
//! ```rust,ignore
1717
//! use aimdb_core::AimDbBuilder;
1818
//! use aimdb_tokio_adapter::TokioAdapter;
19-
//! use aimdb_mqtt_connector::MqttConnector;
19+
//! use aimdb_mqtt_connector::{MqttConnector, MqttLinkExt, MqttOutboundLinkExt};
2020
//! use std::sync::Arc;
2121
//!
2222
//! let runtime = Arc::new(TokioAdapter::new()?);
@@ -26,8 +26,10 @@
2626
//! .with_connector(MqttConnector::new("mqtt://localhost:1883"))
2727
//! .configure::<Temperature>(|reg| {
2828
//! reg.source(temperature_producer)
29-
//! // Outbound: Publish to MQTT
29+
//! // Outbound: Publish to MQTT (QoS/retain via MqttLinkExt traits)
3030
//! .link_to("mqtt://sensors/temperature")
31+
//! .with_qos(1)
32+
//! .with_retain(false)
3133
//! .with_serializer_raw(|t| {
3234
//! serde_json::to_vec(t)
3335
//! .map_err(|_| aimdb_core::connector::SerializeError::InvalidData)
@@ -71,9 +73,12 @@
7173
7274
#![cfg_attr(not(feature = "std"), no_std)]
7375

74-
#[cfg(not(feature = "std"))]
7576
extern crate alloc;
7677

78+
// MQTT knobs over core's generic link builders (works on every feature leg)
79+
pub mod link_ext;
80+
pub use link_ext::{MqttLinkExt, MqttOutboundLinkExt};
81+
7782
// Platform-specific implementations
7883
#[cfg(feature = "tokio-runtime")]
7984
pub mod tokio_client;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//! MQTT-specific knobs for the generic link builders
2+
//!
3+
//! Core's `OutboundConnectorBuilder`/`InboundConnectorBuilder` know schemes
4+
//! and key/value options, never protocol semantics (design 034 §3.6). The
5+
//! MQTT knobs live here as extension traits: importing them makes the MQTT
6+
//! intent explicit at the call site, and the impls push the exact same
7+
//! `("qos", …)` / `("retain", …)` option keys the MQTT clients have always
8+
//! read from `protocol_options` — wire behavior is unchanged.
9+
//!
10+
//! ```rust,ignore
11+
//! use aimdb_mqtt_connector::{MqttLinkExt, MqttOutboundLinkExt};
12+
//!
13+
//! reg.link_to("mqtt://sensors/temp")
14+
//! .with_qos(1)
15+
//! .with_retain(true)
16+
//! .with_serializer_raw(serialize)
17+
//! .finish();
18+
//! ```
19+
20+
use aimdb_core::{InboundConnectorBuilder, OutboundConnectorBuilder};
21+
use alloc::string::ToString;
22+
use core::fmt::Debug;
23+
24+
/// MQTT knobs shared by outbound and inbound links.
25+
pub trait MqttLinkExt: Sized {
26+
/// Sets the MQTT Quality of Service level (0, 1, or 2).
27+
///
28+
/// Outbound: the publish QoS. Inbound: the subscribe QoS. Defaults to
29+
/// QoS 1 when unset (the connectors' own default).
30+
fn with_qos(self, qos: u8) -> Self;
31+
}
32+
33+
/// MQTT knobs that only make sense when publishing.
34+
pub trait MqttOutboundLinkExt: MqttLinkExt {
35+
/// Sets the MQTT retain flag (broker keeps the last message for
36+
/// late-joining subscribers). Defaults to `false` when unset.
37+
fn with_retain(self, retain: bool) -> Self;
38+
}
39+
40+
impl<'r, 'a, T, R> MqttLinkExt for OutboundConnectorBuilder<'r, 'a, T, R>
41+
where
42+
T: Send + Sync + 'static + Debug + Clone,
43+
R: aimdb_executor::RuntimeAdapter + 'static,
44+
{
45+
fn with_qos(self, qos: u8) -> Self {
46+
self.with_config("qos", &qos.to_string())
47+
}
48+
}
49+
50+
impl<'r, 'a, T, R> MqttOutboundLinkExt for OutboundConnectorBuilder<'r, 'a, T, R>
51+
where
52+
T: Send + Sync + 'static + Debug + Clone,
53+
R: aimdb_executor::RuntimeAdapter + 'static,
54+
{
55+
fn with_retain(self, retain: bool) -> Self {
56+
self.with_config("retain", if retain { "true" } else { "false" })
57+
}
58+
}
59+
60+
impl<'r, 'a, T, R> MqttLinkExt for InboundConnectorBuilder<'r, 'a, T, R>
61+
where
62+
T: Send + Sync + 'static + Debug + Clone,
63+
R: aimdb_executor::RuntimeAdapter + 'static,
64+
{
65+
fn with_qos(self, qos: u8) -> Self {
66+
self.with_config("qos", &qos.to_string())
67+
}
68+
}

0 commit comments

Comments
 (0)