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
//! Responses that are described item by item, and the whole-query-string
//! parameter.
//!
//! Everything here needs OpenAPI 3.2:
//!
//! ```text
//! cargo run -p kynos --example streaming --no-default-features \
//! --features openapi32,macros,server,http1,json
//! ```
//!
//! Four things are worth noticing:
//!
//! * **3.1 cannot describe a stream, so the whole subtree is 3.2-only.** What
//! 3.2 adds is `itemSchema`: a way to say what one *item* of a sequential
//! media type looks like, rather than what the whole body looks like. Without
//! it a streaming response could only be described as bytes, and describing
//! it as bytes is the thing this framework exists not to do.
//! * **A stream is `futures_core::Stream` and nothing more.** No executor
//! integration, no combinator crate, no Kynos stream type to adapt to. The
//! hand-written `Countdown` below is the whole contract, which is what keeps
//! the dependency at `futures-core` rather than `futures`.
//! * **The status is committed before the body is.** A stream that fails
//! halfway cannot retract a 200 it already sent, so it terminates. That is a
//! property of streaming rather than of Kynos, and it is why a streamed
//! operation should validate everything it can before returning.
//! * **A 206 can hold several parts, and only 3.2 can say so.**
//! `multipart/byteranges` is a sequential media type whose item *count* the
//! request decides, so there is no array a `schema` could describe — which is
//! what `itemSchema` and `itemEncoding` are for, and what the specification's
//! own *Streaming Byte Ranges* example writes. `RangedParts<T>` is opt-in
//! rather than something enabling `openapi32` switches on, because a feature
//! flag that changed what an existing handler put on the wire would not be
//! the additive thing this one claims to be.
//! * **The same type reads.** `JsonLines<Records<Reading>>` is a request body
//! rather than a response, and the asymmetry is worth noticing: a request
//! record that fails still has a status to spend, because nothing reaches
//! the socket until the handler's future resolves. It is one type in both
//! directions, so it is imported once, from the module that defines it —
//! the responding half is implemented there so a return type reads as a
//! response, and here it is a return type *and* an argument.
//!
//! Server-Sent Events are the fourth sequential media type, and the only one
//! with protocol rules of its own — event names, resumption, reconnection
//! advice. [`sse.rs`](sse.rs) covers them.
//!
//! `QueryString<T, M>` is the other 3.2-only construct here: a parameter whose
//! value is the *entire* query string, media-typed. It exists for the APIs
//! whose filter language is not `key=value` pairs — a JSON filter, an RSQL
//! expression — which 3.1 could describe only by lying about the shape.
use ;
use ;
use ;
/// One reading in a sequence.
/// A finite stream, written by hand.
///
/// `futures_core::Stream` is the entire requirement, so this file needs no
/// stream library at all — which is the point of the demonstration as much as
/// the streaming is.
/// The same, producing raw chunks.
/// The media type an export is served under.
;
/// The filter language this API accepts in a query string.
///
/// Media-typed, because the whole query string is one value in a format —
/// which is exactly what a `key=value` parameter list cannot describe.
;
/// Streams readings as newline-delimited JSON.
///
/// One JSON value per line. `itemSchema` describes the `Reading`, so a consumer
/// knows what one line holds without the description claiming the body is one.
async
/// Streams readings as an RFC 7464 JSON text sequence.
///
/// The same items under a different framing — record-separator delimited rather
/// than newline — which matters when a value can itself contain a newline.
async
/// Streams an export as raw bytes.
///
/// The marker names the media type, exactly as it does for a non-streamed
/// `Binary<M>` body.
async
/// Searches readings with a whole-query-string filter.
///
/// `#[kynos::query]` declares the HTTP `QUERY` method, which OpenAPI 3.2 added
/// a Path Item field for — a search with a body, so a long filter is not a URL
/// of unbounded length. It pairs naturally with a media-typed query string, but
/// the two are independent.
async
/// Serves a recording, whole or in as many parts as were asked for.
///
/// ```text
/// curl -r 0-3,8-11 http://localhost:3000/recording -D -
/// ```
///
/// Three statuses, none chosen at run time: a 200 for a field Kynos cannot
/// apply, a 206 for one it can, and a 416 for one nothing satisfies. The 206
/// declares two shapes, because the request decides which arrives — one part
/// after coalescing is the recording's own media type, and several is
/// `multipart/byteranges` with a `Content-Range` inside each part rather than
/// on the response.
///
/// Overlapping and adjacent parts are merged before anything is written, which
/// is what makes `bytes=0-0,0-0,0-0,...` cost one octet rather than three.
async
/// Ingests readings as newline-delimited JSON, one record at a time.
///
/// The reading half of what `/readings.jsonl` writes, and the request body
/// never exists in memory as a whole. A record that is not JSON ends the
/// stream with a 400; one that is JSON and does not fit `Reading` is a 422
/// naming which record it was, and the record after it still arrives. Both
/// statuses are already on the operation, because they are the ones
/// `BodyRejection` declares.
async
async