alux-http-conformance 0.1.0

One declared HTTP surface and the scenario every interpretation of it must satisfy
Documentation
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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
//! One declared surface, and the domain it reads.
//!
//! This is user code, written once. Every interpretation compiles this same declaration, which is
//! what makes their agreement evidence rather than coincidence.

use crate::SETTLE;
use alux_ext::ext;
use alux_http::{
    BytesOutAlg, CacheControl, ChunksAlg, ChunksExt, EmptyOutAlg, FromPartsAlg, HeaderOutAlg, HtmlOutAlg, HttpApiAlg,
    JsonOutAlg, NamedValuesAlg, PartAlg, RedirectOutAlg, ResultOutAlg, StatusOutAlg, StreamOutAlg, TextOutAlg, http,
};
use alux_shape::Shape;
use core::convert::Infallible;
use core::fmt::Display;
use core::future::Future;
use core::time::Duration;
use serde::{Deserialize, Serialize};
use std::io::{Error as IoError, ErrorKind};
use tokio::time::sleep;

/// Who a caller says they are, sent as cookies.
#[derive(Debug, Serialize, Deserialize, Shape)]
pub struct Session {
    /// Which session the caller is in.
    pub session: String,
}

/// What a caller said about themselves in the headers they sent.
#[derive(Debug, Serialize, Deserialize, Shape)]
pub struct Agent {
    /// What the caller says they are.
    pub user_agent: String,
}

impl NamedValuesAlg for Session {}

impl NamedValuesAlg for Agent {}

/// One reading, sent as a form.
#[derive(Debug, Serialize, Deserialize, Shape)]
pub struct Amount {
    /// What was read.
    pub value: u32,
}

/// Keeps whatever readings the domain has been told about.
pub trait ShopAlg {
    /// Returns the reading recorded for `id`, or why it could not be read.
    fn item(&self, id: u32) -> impl Future<Output = Result<u32, IoError>> + Send;
    /// Returns every reading.
    fn items(&self) -> impl Future<Output = Vec<u32>> + Send;
    /// Records `value` and returns it.
    fn add(&self, value: u32) -> impl Future<Output = u32> + Send;
    /// Notes what was sent and says what it made of it.
    fn note(&self, note: String) -> impl Future<Output = String> + Send;
    /// Forgets every reading.
    fn clear(&self) -> impl Future<Output = ()> + Send;
    /// Returns where the readings actually live.
    fn home(&self) -> impl Future<Output = String> + Send;
    /// Returns the readings as a page.
    fn page(&self) -> impl Future<Output = String> + Send;
    /// Returns the readings as they are stored.
    fn stored(&self) -> impl Future<Output = Vec<u8>> + Send;
    /// Returns who the caller is in, as they said.
    fn who(&self, session: String) -> impl Future<Output = String> + Send;
    /// Returns what the caller says they are.
    fn agent(&self, agent: String) -> impl Future<Output = String> + Send;
    /// Returns the readings and how long they may be kept.
    fn cached(&self) -> impl Future<Output = (String, Vec<u32>)> + Send;
}

/// Derives the operations the shared surface exposes.
#[ext(name = ShopOperationExt, defunc)]
pub impl<This> This
where
    This: ShopAlg,
{
    /// Returns one identified reading, or why it could not be read.
    async fn shop_item(&self, id: u32) -> Result<u32, IoError> {
        self.item(id).await
    }

    /// Returns every reading.
    async fn shop_items(&self) -> Vec<u32> {
        self.items().await
    }

    /// Records one reading and returns it.
    async fn shop_add(&self, value: u32) -> u32 {
        self.add(value).await
    }

    /// Records one reading sent as a form and returns it.
    async fn shop_fill(&self, amount: Amount) -> u32 {
        self.add(amount.value).await
    }

    /// Notes what was sent and says what it made of it.
    async fn shop_note(&self, note: String) -> String {
        self.note(note).await
    }

    /// Forgets every reading.
    async fn shop_clear(&self) {
        self.clear().await;
    }

    /// Returns where the readings actually live.
    async fn shop_home(&self) -> String {
        self.home().await
    }

    /// Returns the readings as a page.
    async fn shop_page(&self) -> String {
        self.page().await
    }

    /// Returns the readings as they are stored.
    async fn shop_stored(&self) -> Vec<u8> {
        self.stored().await
    }

    /// Returns who the caller says they are.
    async fn shop_who(&self, session: Session) -> String {
        self.who(session.session).await
    }

    /// Returns what the caller says they are.
    async fn shop_agent(&self, agent: Agent) -> String {
        self.agent(agent.user_agent).await
    }

    /// Returns the readings, and how long a caller may keep them.
    async fn shop_cached(&self) -> (String, Vec<u32>) {
        self.cached().await
    }
}

/// Declares the shared surface, which every interpretation compiles unchanged.
#[ext(name = ShopApiExt, defunc(via = http))]
pub impl<This> This
where
    This: HttpApiAlg
        + HeaderOutAlg
        + JsonOutAlg
        + TextOutAlg
        + HtmlOutAlg
        + BytesOutAlg
        + EmptyOutAlg
        + RedirectOutAlg
        + StatusOutAlg
        + ResultOutAlg,
{
    /// Declares the surface every interpretation is held to.
    fn shop_api<Alg>(&self)
    where
        Alg: ShopAlg,
    {
        self.routes()
            // One identified reading, its id taken from the path in Poem's spelling.
            .get("/item/:id", self.op(Alg::shop_item).path::<u32>().json().result())
            // Every reading.
            .get("/items", self.op(Alg::shop_items).json())
            // A recording sent as a document, which creates something and says so.
            .post("/items", self.op(Alg::shop_add).body::<u32>().json().status::<201>())
            // The same recording, sent as a form.
            .put("/items", self.op(Alg::shop_fill).form::<Amount>().json())
            // A note, taken exactly as it arrived.
            .patch("/items", self.op(Alg::shop_note).raw_body::<String>().text())
            // A removal, which answers with nothing at all.
            .delete("/items", self.op(Alg::shop_clear).empty())
            // Where the readings actually live.
            .get("/home", self.op(Alg::shop_home).redirect())
            // The readings as a page.
            .get("/page", self.op(Alg::shop_page).html())
            // The readings as they are stored.
            .get("/stored", self.op(Alg::shop_stored).bytes())
            // Who the caller says they are, taken from the cookies they sent.
            .get("/session", self.op(Alg::shop_who).cookie::<Session>().text())
            // What the caller says they are, taken from the headers they sent.
            .get("/agent", self.op(Alg::shop_agent).in_header::<Agent>().text())
            // Every reading, and how long a caller may keep it.
            .get("/cached", self.op(Alg::shop_cached).json().out_header::<CacheControl>())
    }
}

/// The reference domain every interpretation is held to.
#[derive(Debug, Default, Clone, Copy)]
pub struct Shop;

impl ShopAlg for Shop {
    async fn item(&self, id: u32) -> Result<u32, IoError> {
        match id {
            1 => Ok(7),
            _ => Err(IoError::new(ErrorKind::NotFound, "no such reading")),
        }
    }

    async fn items(&self) -> Vec<u32> {
        vec![7]
    }

    async fn add(&self, value: u32) -> u32 {
        value
    }

    async fn note(&self, note: String) -> String {
        format!("noted {note}")
    }

    async fn clear(&self) {}

    async fn home(&self) -> String {
        "/items".to_owned()
    }

    async fn page(&self) -> String {
        "<p>7</p>".to_owned()
    }

    async fn stored(&self) -> Vec<u8> {
        b"seven".to_vec()
    }

    async fn who(&self, session: String) -> String {
        format!("known as {session}")
    }

    async fn agent(&self, agent: String) -> String {
        format!("sent by {agent}")
    }

    async fn cached(&self) -> (String, Vec<u32>) {
        ("max-age=60".to_owned(), vec![7])
    }
}

/// Every label the declared surface states, in declaration order.
pub const LABELS: &[&str] = &[
    "GET /item/{id}",
    "GET /items",
    "POST /items",
    "PUT /items",
    "PATCH /items",
    "DELETE /items",
    "GET /home",
    "GET /page",
    "GET /stored",
    "GET /session",
    "GET /agent",
    "GET /cached",
];

/// Reads however many arguments a caller states.
pub trait WideAlg {
    /// Returns what the domain makes of everything stated.
    fn wide(&self, stated: Vec<String>) -> impl Future<Output = String> + Send;
}

/// Derives the one operation the widest surface exposes.
#[ext(name = WideOperationExt, defunc)]
pub impl<This> This
where
    This: WideAlg,
{
    /// Returns what the domain makes of sixteen stated arguments.
    #[allow(clippy::too_many_arguments)]
    async fn wide_all(
        &self,
        first: String,
        second: String,
        third: String,
        fourth: String,
        fifth: String,
        sixth: String,
        seventh: String,
        eighth: String,
        ninth: String,
        tenth: String,
        eleventh: String,
        twelfth: String,
        thirteenth: String,
        fourteenth: String,
        fifteenth: String,
        sixteenth: String,
    ) -> String {
        let stated = vec![
            first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh, twelfth, thirteenth,
            fourteenth, fifteenth, sixteenth,
        ];

        self.wide(stated).await
    }
}

/// Declares the widest endpoint the specification states, which every interpretation compiles.
///
/// Sixteen is what the products accumulate to, so one endpoint reading sixteen arguments is what
/// holds every interpretation to the same width. One role is repeated because a role a framework
/// reads once is a framework's business, and what is being stated here is the width.
#[ext(name = WideApiExt, defunc(via = http))]
pub impl<This> This
where
    This: HttpApiAlg + TextOutAlg,
{
    /// Declares one endpoint reading sixteen arguments.
    fn wide_api<Alg>(&self)
    where
        Alg: WideAlg,
    {
        self.routes().post(
            "/wide",
            self.op(Alg::wide_all)
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .raw_body::<String>()
                .text(),
        )
    }
}

impl WideAlg for Shop {
    async fn wide(&self, stated: Vec<String>) -> String {
        stated.len().to_string()
    }
}

/// A body the domain produces a piece at a time.
///
/// Nothing here names a stream type. The domain states what a chunk is and how the next one is
/// taken, and whichever interpretation carries the answer chooses how the bytes actually move.
#[derive(Debug, Default)]
pub struct Ticks {
    left: Vec<&'static str>,
}

impl ChunksAlg for Ticks {
    type Chunk = Vec<u8>;
    type Error = Infallible;

    async fn next_chunk(&mut self) -> Option<Result<Self::Chunk, Self::Error>> {
        self.left.pop().map(|tick| Ok(tick.as_bytes().to_vec()))
    }
}

/// Answers with a body it produces a piece at a time.
pub trait TicksAlg {
    /// Returns what the domain has to say, a piece at a time.
    fn ticks(&self) -> impl Future<Output = Ticks> + Send;
}

impl TicksAlg for Shop {
    async fn ticks(&self) -> Ticks {
        Ticks { left: vec!["three", "two", "one"] }
    }
}

/// Derives the one operation the streamed surface exposes.
#[ext(name = TicksOperationExt, defunc)]
pub impl<This> This
where
    This: TicksAlg,
{
    /// Returns what the domain has to say, a piece at a time.
    async fn shop_ticks(&self) -> Ticks {
        self.ticks().await
    }
}

/// Declares the streamed surface, which every interpretation carrying a produced body compiles.
#[ext(name = StreamApiExt, defunc(via = http))]
pub impl<This> This
where
    This: HttpApiAlg + StreamOutAlg,
{
    /// Declares one endpoint answering with a body produced over time.
    fn stream_api<Alg>(&self)
    where
        Alg: TicksAlg,
    {
        self.routes().get("/ticks", self.op(Alg::shop_ticks).stream())
    }
}

/// How long after a close begins the slow endpoint would answer.
///
/// Far longer than any drain an interpretation states, so a caller waiting on it is a caller whose
/// connection outlives the close rather than one the close waits out.
pub const ANSWERS_LATE: Duration = Duration::from_secs(30);

/// How long after a close begins the pausing endpoint answers.
///
/// Shorter than any drain an interpretation states, so a caller waiting on it is answered while the
/// server is closing rather than cut off by it.
pub const ANSWERS_SOON: Duration = Duration::from_secs(1);

/// How long the slow endpoint takes to answer.
pub const SLOW: Duration = ANSWERS_LATE.saturating_add(SETTLE);

/// How long the pausing endpoint takes to answer.
///
/// Both endpoints carry [`SETTLE`], the wait between sending a request and beginning the close, so
/// each answers the stated time after the close begins rather than after the request. That is what
/// lets a measurement be read against the drain directly.
pub const PAUSE: Duration = ANSWERS_SOON.saturating_add(SETTLE);

/// Answers after taking some time, which is how a caller holds a request in flight.
pub trait SlowAlg {
    /// Returns what the domain has to say, once it has taken longer than any drain.
    fn slow(&self) -> impl Future<Output = String> + Send;

    /// Returns what the domain has to say, once it has paused for less than any drain.
    fn pause(&self) -> impl Future<Output = String> + Send;
}

impl SlowAlg for Shop {
    async fn slow(&self) -> String {
        sleep(SLOW).await;
        "waited".to_owned()
    }

    async fn pause(&self) -> String {
        sleep(PAUSE).await;
        "paused".to_owned()
    }
}

/// Derives the one operation the slow surface exposes.
#[ext(name = SlowOperationExt, defunc)]
pub impl<This> This
where
    This: SlowAlg,
{
    /// Answers slowly enough that the caller is still waiting when the server closes.
    ///
    /// Takes longer to answer than any interpretation waits before closing anyway, so a caller
    /// asking for this holds a connection the server is still producing an answer on. It exists for
    /// the lifecycle scenario, which needs a connection that outlives a close.
    async fn shop_slow(&self) -> String {
        self.slow().await
    }

    /// Answers while the server is closing, rather than after it has closed.
    ///
    /// Takes less time to answer than any interpretation waits before closing anyway, so a caller
    /// asking for this is answered by a server that is already shutting down. It is the other half
    /// of what the lifecycle scenario needs: a request in flight that a close does not cut off.
    async fn shop_pause(&self) -> String {
        self.pause().await
    }
}

/// Declares the surface the lifecycle scenario serves.
///
/// Three endpoints and nothing else. One answers at once, so a caller can state that a server is
/// serving. The other two take time, which is how a caller holds a request in flight across a
/// close: one takes longer than any drain, the other less. None can fail, because being busy is not
/// a failure.
#[ext(name = LifecycleApiExt, defunc(via = http))]
pub impl<This> This
where
    This: HttpApiAlg + JsonOutAlg + TextOutAlg,
{
    /// Declares one endpoint answering at once and one taking longer than any drain.
    fn lifecycle_api<Alg>(&self)
    where
        Alg: ShopAlg + SlowAlg,
    {
        self.routes()
            // Something answered at once, which is how a caller states that a server is serving.
            .get("/items", self.op(Alg::shop_items).json())
            // Something not answered in any useful time, which is how a caller holds a connection
            // that outlives the close.
            .get("/slow", self.op(Alg::shop_slow).text())
            // Something answered after a moment, which is how a caller holds a request the close
            // has time to finish.
            .get("/pause", self.op(Alg::shop_pause).text())
    }
}

/// What a caller sent as parts, read the same way by every interpretation.
///
/// Nothing here names a reader. The domain states what it makes of a sequence of parts, and each
/// interpretation hands it whichever reader it has.
#[derive(Debug, Default)]
pub struct Upload {
    /// What each part was sent under, and what it carried.
    pub stated: Vec<String>,
}

impl<Parts> FromPartsAlg<Parts> for Upload
where
    Parts: ChunksAlg + Send,
    Parts::Error: Display + Send,
    Parts::Chunk: PartAlg + Send,
    <Parts::Chunk as PartAlg>::Content: ChunksAlg<Chunk = Vec<u8>> + Send + 'static,
    <<Parts::Chunk as PartAlg>::Content as ChunksAlg>::Error: Display,
{
    type Error = String;

    async fn from_parts(mut parts: Parts) -> Result<Self, Self::Error> {
        let mut stated = Vec::new();
        while let Some(part) = parts.next_chunk().await {
            let part = part.map_err(|error| error.to_string())?;
            let name = part.part_name().unwrap_or_default().to_owned();
            let carried = part.part_content().gathered().await.map_err(|error| error.to_string())?;
            let carried = String::from_utf8_lossy(&carried.concat()).into_owned();
            stated.push(format!("{name}={carried}"));
        }

        Ok(Self { stated })
    }
}

/// Reads a body a caller sent as parts.
pub trait UploadAlg {
    /// Returns what the domain makes of everything the parts stated.
    fn uploaded(&self, stated: Vec<String>) -> impl Future<Output = String> + Send;
}

impl UploadAlg for Shop {
    async fn uploaded(&self, stated: Vec<String>) -> String {
        stated.join(",")
    }
}

/// Derives the one operation the parts surface exposes.
#[ext(name = UploadOperationExt, defunc)]
pub impl<This> This
where
    This: UploadAlg,
{
    /// Returns what the domain makes of a body sent as parts.
    async fn shop_upload(&self, upload: Upload) -> String {
        self.uploaded(upload.stated).await
    }
}

/// Declares the parts surface, which every interpretation reading a body as parts compiles.
#[ext(name = MultipartApiExt, defunc(via = http))]
pub impl<This> This
where
    This: HttpApiAlg + TextOutAlg,
{
    /// Declares one endpoint reading a body that arrives as parts.
    fn multipart_api<Alg>(&self)
    where
        Alg: UploadAlg,
    {
        self.routes().post("/upload", self.op(Alg::shop_upload).multipart::<Upload>().text())
    }
}