1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//! [`MqttContext`]: what a delivery carries beyond its payload and headers.
//!
//! MQTT publishes to a topic and subscribes with a filter, so the two are not the same string
//! whenever a filter carries a wildcard. The subscription name a handler reads with `ctx.name()`
//! is the filter; the topic the message was actually published to is here, under the
//! [`DeliveryTopic`] key.
use ;
use crateMqttMessage;
/// The per-delivery context of this broker.
///
/// Read a field with a key rather than by field access: `ctx.context(DeliveryTopic)` in a handler
/// body, `cx.context(DeliveryTopic)` in a publish transform on the reply or the retry position.
///
/// # Examples
///
/// ```
/// # mod demo {
/// use ruststream_rumqttc::prelude::*;
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Telemetry {
/// temperature: f64,
/// }
///
/// #[subscriber(MqttFilter::new("devices/+/telemetry"))]
/// async fn record(telemetry: &Telemetry, ctx: &mut Context<'_, MqttContext>) -> HandlerOutcome {
/// let device = ctx.context(DeliveryTopic).split('/').nth(1).unwrap_or_default();
/// tracing::info!(device, telemetry.temperature, "reading recorded");
/// HandlerOutcome::ack()
/// }
///
/// #[ruststream::app]
/// fn app() -> impl App {
/// RustStream::new(AppInfo::new("telemetry", "0.1.0")).with_broker(
/// MqttBroker::new("mqtt://localhost:1883", "telemetry-svc"),
/// |b| {
/// b.include(record)
/// .out_retry(Publish::default())
/// .to("devices/retry/telemetry");
/// },
/// )
/// }
/// # }
/// # fn main() {}
/// ```
/// The key for [`MqttContext::topic`].
///
/// It reads the topic borrowed through `ctx.context(DeliveryTopic)`, and owned through the
/// `Ctx<DeliveryTopic>` extractor, which is also what fixes a handler's context type to
/// [`MqttContext`] without a `Context` parameter.
///
/// # Examples
///
/// ```
/// # #[cfg(feature = "testing")]
/// # mod demo {
/// use std::error::Error;
///
/// use ruststream::testing::TestApp;
/// use ruststream_rumqttc::prelude::*;
/// use serde::{Deserialize, Serialize};
///
/// #[derive(Deserialize, Serialize, Outgoing)]
/// struct Telemetry {
/// temperature: f64,
/// }
///
/// #[derive(Debug, PartialEq, Deserialize, Serialize, Outgoing)]
/// #[outgoing(name = "alerts")]
/// struct Alert {
/// topic: String,
/// }
///
/// #[subscriber(MqttFilter::new("devices/+/telemetry"))]
/// async fn watch(
/// telemetry: &Telemetry,
/// Ctx(topic): Ctx<DeliveryTopic>,
/// Out(alerts): Out<impl Publisher>,
/// ) -> HandlerOutcome {
/// if telemetry.temperature <= 30.0 {
/// return HandlerOutcome::ack();
/// }
/// if alerts.message(&Alert { topic }).publish().await.is_err() {
/// return HandlerOutcome::retry();
/// }
/// HandlerOutcome::ack()
/// }
///
/// pub fn app() -> impl App {
/// RustStream::new(AppInfo::new("telemetry", "0.1.0")).with_broker(
/// MqttBroker::new("mqtt://localhost:1883", "telemetry-svc"),
/// |b| {
/// b.include(watch)
/// .out(DefaultSlot, Publish::default())
/// .out_retry(Publish::default())
/// .to("devices/retry/telemetry")
/// .build();
/// },
/// )
/// }
///
/// pub async fn an_alert_names_the_topic_not_the_filter() -> Result<(), Box<dyn Error>> {
/// let tb = TestApp::start(app()).await?;
///
/// tb.broker::<MqttBroker>()
/// .message(&Telemetry { temperature: 31.5 })
/// .to("devices/dev42/telemetry")
/// .publish()
/// .await?;
///
/// tb.broker::<MqttBroker>()
/// .published::<Alert>("alerts")
/// .assert_called_once()
/// .with(&Alert { topic: "devices/dev42/telemetry".to_owned() });
/// tb.shutdown().await?;
/// Ok(())
/// }
/// # }
/// # #[cfg(feature = "testing")]
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # tokio::runtime::Builder::new_multi_thread()
/// # .enable_all()
/// # .build()?
/// # .block_on(demo::an_alert_names_the_topic_not_the_filter())
/// # }
/// # #[cfg(not(feature = "testing"))]
/// # fn main() {}
/// ```
;