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
//! Message-type metadata for `AsyncAPI` generation.
/// Metadata about a message type, used to name and describe it in an `AsyncAPI` document.
///
/// Derive with `#[derive(MessageInfo)]` (requires the `macros` feature): the derive uses the type's
/// name as [`NAME`](Self::NAME) and its doc comment as [`DESCRIPTION`](Self::DESCRIPTION).
///
/// # Examples
///
/// ```
/// use ruststream::prelude::*;
///
/// struct Order;
/// impl MessageInfo for Order {
/// const NAME: &'static str = "Order";
/// }
///
/// assert_eq!(Order::NAME, "Order");
/// ```
/// Declares a message type's typed header contract.
///
/// `#[derive(Outgoing)]` implements it: `#[outgoing(headers = Meta)]` on the type sets the
/// contract to [`WithHeaders<Meta>`], and a declaration without it sets [`NoHeaders`]
/// (`#[derive(MessageInfo)]` with `#[message(headers(Meta))]` does the same for a type that
/// declares no destination). The contract types both message documentation (the `AsyncAPI`
/// headers schema of the message) and the publish builder: `publish()` compiles only with the
/// headers the contract declares (none for [`NoHeaders`]).
///
/// The two contract shapes are a closed vocabulary: [`HeadersContract`] is sealed.
///
/// # Examples
///
/// ```
/// use ruststream::prelude::*;
///
/// struct ChunkMeta {
/// task_id: u64,
/// }
///
/// struct ChunkDone;
///
/// // What `#[derive(Outgoing)]` + `#[outgoing(headers = ChunkMeta)]` generates:
/// impl MessageHeaders for ChunkDone {
/// type Contract = WithHeaders<ChunkMeta>;
/// }
/// ```
/// The header contract of a message that carries no typed headers.
///
/// See [`MessageHeaders`]; a `#[derive(MessageInfo)]` without a `#[message(headers(..))]` attribute
/// declares this contract.
;
// Bare collection payloads are common message bodies (a batch as `Vec<Item>`, a plain text
// message) but cannot derive `Message`, and the orphan rule keeps downstream crates from
// implementing this foreign trait on them - so the contract lives here: none. A collection
// payload that needs a header contract wraps in a `#[derive(MessageInfo)]` newtype.
/// The header contract of a message whose headers are the typed struct `H`.
///
/// See [`MessageHeaders`]; `#[message(headers(H))]` on a `#[derive(MessageInfo)]` type declares
/// this contract. `H` is serialized into the header map on the typed publish path and lifted
/// into the message's `AsyncAPI` headers schema when it implements [`schemars::JsonSchema`].
>);
/// The closed set of header contract shapes: [`NoHeaders`] and [`WithHeaders<H>`](WithHeaders).
///
/// Sealed: the publish machinery matches on exactly these two shapes, so the vocabulary cannot
/// grow outside the crate.
/// Declares where a message type is sent.
///
/// `#[derive(Outgoing)]` implements it from the type's `#[outgoing(name = ..)]` attribute, and
/// [`Form`](Self::Form) is what the publish builder reads to decide which destination position
/// the call site has to fill:
///
/// * `#[outgoing(name = "orders.done")]` declares [`FixedName`]: the destination is resolved by
/// the declaration, and the builder offers no `to(..)` at all.
/// * `#[outgoing(name = "orders.{tenant}.v1")]` declares [`NameTemplate`]: the builder's `to()`
/// opens one setter per placeholder, and the publish terminal appears once every one of them
/// is bound.
/// * a derive with no `name` declares [`CallerName`]: the builder takes the destination as
/// `to("orders.archived")`.
///
/// In every case the destination exists before the publish does, so an under-specified call site
/// is a compile error rather than a runtime surprise.
///
/// # Examples
///
/// ```
/// use ruststream::prelude::*;
///
/// struct OrderDone;
///
/// // What `#[derive(Outgoing)]` + `#[outgoing(name = "orders.done")]` generates:
/// impl OutgoingDestination for OrderDone {
/// type Form = FixedName;
/// const DESTINATION: &'static str = "orders.done";
/// }
///
/// assert_eq!(OrderDone::DESTINATION, "orders.done");
/// ```
// The same two bare payloads that carry no header contract declare no destination either: a
// `Vec<Item>` batch body and a plain text message are sent wherever the call site says. The
// orphan rule keeps a downstream crate from declaring it, so the declaration lives here.
/// The destination form of a type declaring one literal name: the name is fixed, so the
/// publish builder resolves the destination without asking the call site.
;
/// The destination form of a type declaring a name template: the call site binds every
/// placeholder through the setters generated from it, and the name is rendered per publish.
;
/// The destination form of a type declaring no name: the call site names the destination.
;
/// The closed set of destination forms: [`FixedName`], [`NameTemplate`] and [`CallerName`].
///
/// Sealed, like [`HeadersContract`]: the publish builder branches on exactly these three shapes
/// at compile time, so the vocabulary cannot grow outside the crate.