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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
//! Subscription descriptors: how a handler is bound to one broker subscription.
//!
//! A [`SubscriptionSource`] is the value a broker crate exposes as its subscriber configuration:
//! it carries everything needed to open one subscription (subject / name, consumer group,
//! durable name, delivery policy, ...) and knows how to turn that into a live [`Subscriber`]
//! against a connected broker. The default [`Name`] source covers brokers that only need a name
//! string (those implementing [`Subscribe`]); richer brokers ship their own sources.
//!
//! This is the seam the `#[subscriber(..)]` macro and the application object build on: the macro
//! takes a source (a name string or a broker config value), the runtime resolves it once against
//! the [`ConnectedBroker`] form produced by [`Broker::connect`](crate::Broker::connect).
use ;
use Error;
use crate::;
/// A description of one subscription, resolved against a connected broker at startup.
///
/// The runtime calls [`subscribe`] once, against the [`ConnectedBroker`] witness produced by
/// [`Broker::connect`](crate::Broker::connect), to obtain the live [`Subscriber`]. The associated
/// [`Subscriber`](Self::Subscriber) type lives on the source rather than the broker, so a single
/// broker can offer several subscription kinds with different subscriber types (for example
/// `Redis` pub/sub versus streams).
///
/// [`subscribe`]: Self::subscribe
///
/// # Examples
///
/// ```
/// use ruststream::{ConnectedBroker, SubscriptionSource};
///
/// async fn open<C, S>(source: S, connected: &C) -> Result<S::Subscriber, C::Error>
/// where
/// C: ConnectedBroker,
/// S: SubscriptionSource<C>,
/// {
/// source.subscribe(connected).await
/// }
/// ```
/// The default [`SubscriptionSource`]: subscribe by name string via the [`Subscribe`] capability.
///
/// Produced by `#[subscriber("name")]` and usable directly with any connected broker implementing
/// [`Subscribe`].
///
/// # Examples
///
/// ```
/// use ruststream::{Name, Subscribe, SubscriptionSource};
///
/// async fn open<C: Subscribe>(connected: &C) -> Result<C::Subscriber, C::Error> {
/// Name::new("orders").subscribe(connected).await
/// }
/// ```
;
/// A source decorator handing out the subscription's [`Seeker`] through a
/// [`SeekerToken`] minted before registration.
///
/// The runtime owns the subscriber for the life of the service, so application code cannot call
/// [`Seekable::seeker`] itself. `attach` wraps any [`SubscriptionSource`] whose subscriber is
/// [`Seekable`] and returns the token alongside it: mount the wrapped source anywhere a source
/// goes, and once the runtime opens the subscription at startup the token resolves to the live
/// seeker. On a broker without the [`Seekable`] capability the wrapped source does not implement
/// [`SubscriptionSource`], so the mount fails to compile instead of failing at runtime.
///
/// # Examples
///
/// ```
/// # #[cfg(feature = "memory")]
/// # async fn demo() -> Result<(), Box<dyn std::error::Error>> {
/// use ruststream::memory::{MemoryBroker, MemoryPosition, MemorySource};
/// use ruststream::{Broker, Seeker, SubscriptionSource, WithSeeker};
///
/// let (source, token) = WithSeeker::attach(MemorySource::new("orders"));
///
/// // The runtime does this at startup; the token resolves once the subscription is open.
/// let connected = MemoryBroker::new().connect().await?;
/// let subscriber = source.subscribe(&connected).await?;
///
/// token.seeker()?.seek(MemoryPosition::start()).await?;
/// # let _ = subscriber;
/// # Ok(())
/// # }
/// ```
/// A source decorator opening the subscription at a chosen position instead of the broker's
/// default.
///
/// Wraps any [`SubscriptionSource`] whose subscriber is [`Seekable`] and seeks to `position`
/// before the first delivery, so the handler never sees a message from before the chosen
/// point. The position is the broker's own type (its latest / earliest constructors, a
/// sequence number, a captured [`Positioned`](crate::Positioned) value), which makes "start
/// from the latest on deploy" or "replay the whole log into a fresh subscription" a
/// declaration at the mount site rather than an operational action afterwards. On a broker
/// without the [`Seekable`] capability the wrapped source does not implement
/// [`SubscriptionSource`], so the mount fails to compile.
///
/// This is a forced position: it applies on every startup. A conditional default (only when
/// the broker has no stored cursor for the group) remains the domain of the broker's own
/// subscription descriptor.
///
/// # Examples
///
/// ```
/// # #[cfg(feature = "memory")]
/// # async fn demo() -> Result<(), Box<dyn std::error::Error>> {
/// use futures::StreamExt;
/// use ruststream::memory::{MemoryBroker, MemoryPosition, MemorySource};
/// use ruststream::{Broker, IncomingMessage, OutgoingMessage, Publisher, StartAt};
/// use ruststream::{Subscriber, SubscriptionSource};
///
/// let connected = MemoryBroker::new().connect().await?;
/// let publisher = connected.publisher();
/// publisher.publish(OutgoingMessage::new("audit", b"one".as_slice())).await?;
///
/// // A fresh subscription opened at the start of the log replays the earlier publish.
/// let mut subscriber = StartAt::new(MemorySource::new("audit"), MemoryPosition::start())
/// .subscribe(&connected)
/// .await?;
/// let mut stream = std::pin::pin!(subscriber.stream());
/// let replayed = stream.next().await.expect("replayed")?;
/// assert_eq!(replayed.payload(), b"one");
/// replayed.ack().await?;
/// # Ok(())
/// # }
/// ```
/// Resolves the [`Seeker`] of a subscription mounted through [`WithSeeker::attach`].
///
/// Clonable and cheap: hand one copy to an admin endpoint, keep another in the application
/// state. The token resolves once the runtime has opened the subscription (startup), so redeem
/// it in an [`after_startup`](crate::runtime::RustStream::after_startup) hook, from
/// [`RunningApp`](crate::runtime::RunningApp)-scoped code, or inside a handler.
///
/// # Examples
///
/// ```
/// # #[cfg(feature = "memory")]
/// # fn demo() {
/// use ruststream::WithSeeker;
/// use ruststream::memory::{MemorySeeker, MemorySource};
///
/// let (source, token) = WithSeeker::<_, MemorySeeker>::attach(MemorySource::new("orders"));
/// // Not opened yet: redeeming before startup reports the pending state.
/// assert!(token.seeker().is_err());
/// # let _ = source;
/// # }
/// ```
/// The subscription behind a [`SeekerToken`] has not been opened yet.
;