kynos 0.3.0

An idiomatic, performance-focused REST API framework with OpenAPI 3.1 and 3.2 support.
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
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
//! What each interceptor writes onto a response, against what it declared.
//!
//! One reason: `Adds` and `NAMES` are the whole of an interceptor's promise
//! about response headers, and the compiler checks that two interceptors do not
//! *collide* on a name — never that either sets the names it claimed, and never
//! that it sets nothing else. Both halves are asserted here, over a live
//! service, because both are how a response and its description come apart.
//!
//! This does not assert `describe` directly. `middleware/erased.rs` restates
//! the associated types and asserting it back would compare the mechanism to
//! itself; only the conformance matrix can find that wrong.

#![cfg(all(feature = "macros", feature = "json"))]

use std::collections::BTreeSet;

use kynos::{
    extract::params::header::HeaderParams,
    http::StatusCode,
    middleware::{
        cors::Cors,
        request_id::{RequestId, XRequestId},
    },
};

#[path = "support/mod.rs"]
mod support;

use support::{App, get};

/// The response headers a bare service sends, so a later comparison can name
/// what an interceptor *added* rather than what was there anyway.
async fn baseline() -> BTreeSet<String> {
    fields(&get(&support::service(), "/users/1").call().await)
}

fn fields(reply: &support::Reply) -> BTreeSet<String> {
    reply
        .headers
        .keys()
        .map(|name| name.as_str().to_owned())
        .collect()
}

/// Every name in `NAMES`, lowercased the way a `HeaderMap` key is.
fn declared<H: HeaderParams>() -> BTreeSet<String> {
    H::NAMES
        .iter()
        .map(|name| name.to_ascii_lowercase())
        .collect()
}

/// `RequestId` sets exactly the one name its header group declares.
#[tokio::test]
async fn a_request_id_sets_the_name_its_group_declares_and_no_other() {
    let service = support::router()
        .intercept(RequestId::new())
        .build(App::new())
        .expect("a describable router");

    let reply = get(&service, "/users/1").call().await;
    assert_eq!(reply.status, StatusCode::OK);

    let added: BTreeSet<String> = fields(&reply)
        .difference(&baseline().await)
        .cloned()
        .collect();

    assert_eq!(added, declared::<XRequestId>());
    assert!(
        reply
            .field("x-request-id")
            .is_some_and(|value| !value.is_empty()),
        "the declared name was set to nothing"
    );
}

/// The pass control: without the interceptor the name is absent, so the case
/// above is about the interceptor rather than about the fixture.
#[tokio::test]
async fn a_service_without_a_request_id_sets_no_such_name() {
    let reply = get(&support::service(), "/users/1").call().await;

    assert!(reply.field("x-request-id").is_none());
}

/// A client-supplied id is honoured only where it was trusted.
///
/// Both directions, because trusting by default would let a client choose its
/// own correlation id and collide with another's on purpose.
#[tokio::test]
async fn a_client_supplied_id_is_used_only_when_it_was_trusted() {
    let trusting = support::router()
        .intercept(RequestId::new().trust_client(true))
        .build(App::new())
        .expect("a describable router");

    let trusted = get(&trusting, "/users/1")
        .header("x-request-id", "from-the-client")
        .call()
        .await;
    assert_eq!(
        trusted.field("x-request-id").as_deref(),
        Some("from-the-client")
    );

    let ignored = get(&support::service(), "/users/1")
        .header("x-request-id", "from-the-client")
        .call()
        .await;
    assert!(ignored.field("x-request-id").is_none());

    let untrusting = support::router()
        .intercept(RequestId::new())
        .build(App::new())
        .expect("a describable router");

    let replaced = get(&untrusting, "/users/1")
        .header("x-request-id", "from-the-client")
        .call()
        .await;
    assert_ne!(
        replaced.field("x-request-id").as_deref(),
        Some("from-the-client"),
        "an untrusted client id was echoed back"
    );
}

/// `Cors` adds its own names to a permitted cross-origin response and nothing
/// beyond them.
#[tokio::test]
async fn cors_adds_only_the_names_it_declares() {
    let service = support::router()
        .intercept(Cors::new().allow_origins(["https://app.example.com"]))
        .build(App::new())
        .expect("a describable router");

    let reply = get(&service, "/users/1")
        .header("origin", "https://app.example.com")
        .call()
        .await;

    let added: BTreeSet<String> = fields(&reply)
        .difference(&baseline().await)
        .cloned()
        .collect();

    assert_eq!(
        added,
        ["access-control-allow-origin", "vary"]
            .into_iter()
            .map(str::to_owned)
            .collect::<BTreeSet<_>>()
    );
}

/// Two interceptors that both contribute a `Vary` field union rather than
/// clobbering, which is the one response header whose contributors compose.
#[cfg(feature = "compression")]
#[tokio::test]
async fn two_interceptors_contributing_vary_both_appear_in_it() {
    use kynos::middleware::compression::Compression;

    let service = support::router()
        .intercept(Cors::new().allow_origins(["https://app.example.com"]))
        .intercept(Compression::new())
        .build(App::new())
        .expect("a describable router");

    let reply = get(&service, "/users/1")
        .header("origin", "https://app.example.com")
        .header("accept-encoding", "gzip")
        .call()
        .await;

    let vary = reply.field("vary").expect("a Vary field");
    let names: BTreeSet<String> = vary
        .split(',')
        .map(|name| name.trim().to_ascii_lowercase())
        .collect();

    assert!(names.contains("origin"), "{vary}");
    assert!(names.contains("accept-encoding"), "{vary}");
}

// --- The two closed sets --------------------------------------------------

/// Every interceptor Kynos ships, named against the set this suite accounts
/// for.
///
/// Witnessing a set someone chose says nothing about whether the set is the
/// whole set. The declared side is therefore read off disk — every `.rs` file
/// under `src/middleware/`, walked rather than transcribed — so an interceptor
/// added in a module no list mentions still fails the build.
///
/// Naming the types rather than counting them is what makes the failure
/// readable: a count says two numbers differ, a set says which interceptor
/// nothing accounts for. It is also what lets two branches each add one and
/// merge, since alphabetical insertion puts them on different lines.
///
/// Neither side is `#[cfg]`-gated any more. Both are source text, and a file
/// exists on disk whether or not the feature that compiles it is on, so this
/// now holds at baseline features as well as under `--all-features`.
#[test]
fn every_interceptor_kynos_ships_is_accounted_for() {
    /// Sorted. `Trace` is an `Observer` rather than an `Interceptor` — it
    /// declares nothing, so it is not in this set and is named below instead.
    const WITNESSED: &[&str] = &[
        "BodySize",
        "BodyTimeout",
        "Cache",
        "Compression",
        "Concurrency",
        "Conditional",
        "Cors",
        "Csrf",
        "Decompression",
        "RateLimit",
        "RequestId",
        "SetCookies",
        "Timeout",
    ];

    let declared = implementors_of("> Interceptor<C> for ");

    assert_eq!(
        declared,
        WITNESSED
            .iter()
            .map(|name| (*name).to_owned())
            .collect::<BTreeSet<_>>(),
        "`middleware/` implements `Interceptor` for a different set than this suite accounts \
         for; an interceptor added without a case is one whose declaration nothing reads"
    );
}

/// Every observer Kynos ships, named the same way.
///
/// A separate set because an observer declares *nothing*: it cannot add a
/// header or short-circuit, which is exactly why `Trace` needs no
/// header-versus-declaration case and does need to be accounted for somewhere.
///
/// The list this replaced opened ten files and `compression.rs` was not among
/// them, so an `Observer` implemented there would have been counted by nothing.
/// Walking the directory is what closes that.
#[test]
fn every_observer_kynos_ships_is_accounted_for() {
    /// Sorted.
    const WITNESSED: &[&str] = &["Trace"];

    let declared = implementors_of("> Observer<C> for ");

    assert_eq!(
        declared,
        WITNESSED
            .iter()
            .map(|name| (*name).to_owned())
            .collect::<BTreeSet<_>>(),
        "`middleware/` implements `Observer` for a different set than this suite accounts for"
    );
}

/// The type names `middleware/` implements `marker` for, read off disk.
///
/// `marker` carries the `> ` that closes the impl's generic list, which is what
/// keeps `Interceptor<C>` from also matching `ErasedInterceptor<C>`.
fn implementors_of(marker: &str) -> BTreeSet<String> {
    let mut sources = Vec::new();
    collect_sources(
        std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src/middleware")),
        &mut sources,
    );
    assert!(
        !sources.is_empty(),
        "no sources found under `src/middleware/`"
    );

    sources
        .iter()
        .flat_map(|source| {
            source
                .match_indices(marker)
                .map(|(at, _)| {
                    source[at + marker.len()..]
                        .chars()
                        .take_while(|character| character.is_alphanumeric() || *character == '_')
                        .collect::<String>()
                })
                .collect::<Vec<_>>()
        })
        .collect()
}

/// Every `.rs` file under `directory`, read, except a sibling `tests.rs`.
///
/// Test modules are excluded because a fixture implementing `Interceptor` is
/// not something Kynos ships, and the transcribed list this replaced named no
/// `tests.rs` either.
fn collect_sources(directory: &std::path::Path, into: &mut Vec<String>) {
    let mut entries: Vec<_> = std::fs::read_dir(directory)
        .unwrap_or_else(|error| panic!("read `{}`: {error}", directory.display()))
        .map(|entry| entry.expect("read a directory entry").path())
        .collect();
    entries.sort();

    for path in entries {
        if path.is_dir() {
            collect_sources(&path, into);
        } else if path.extension().is_some_and(|extension| extension == "rs")
            && path.file_name().is_some_and(|name| name != "tests.rs")
        {
            into.push(
                std::fs::read_to_string(&path)
                    .unwrap_or_else(|error| panic!("read `{}`: {error}", path.display())),
            );
        }
    }
}

// --- The short circuits, and the body each owes its description -----------

/// Sorted. `Infallible` is in the set and has no case in the sweep below: it is
/// uninhabited, so there is no value to drive and no status to declare.
const SHORT_CIRCUITS: &[&str] = &[
    "AtCapacity",
    "BodySizeExceeded",
    "CrossSite",
    "Infallible",
    "NotAcceptable",
    "NotModified",
    "RateLimited",
    "RateLimitedFields",
    "TimedOut",
    "Undecodable",
];

/// The members of `SHORT_CIRCUITS` that have no value to drive.
///
/// Membership is for a type no value of which can exist -- `Infallible` is
/// uninhabited, so there is nothing to hand `case` and nothing to put on a
/// wire. It is *not* for a type that is merely awkward to construct: every name
/// here is one the sweep stops asserting anything about. Widening it is how the
/// sweep would be silenced rather than satisfied, so a type that can be built
/// gets built, even where building it takes a fixture.
const UNCONSTRUCTIBLE: &[&str] = &["Infallible"];

/// Each excluded name beside the `STATUSES` of the type it stands for.
///
/// `UNCONSTRUCTIBLE` is `&[&str]`, and a string cannot be asked what it
/// declares -- which is why the exclusion went unchecked. This is the join
/// between the two, written once so that the check below reaches a type rather
/// than a name.
const UNCONSTRUCTIBLE_STATUSES: &[(&str, &[u16])] = &[(
    "Infallible",
    <std::convert::Infallible as kynos::response::ShortCircuit>::STATUSES,
)];

/// Every exclusion is earned rather than asserted.
///
/// Two halves, and neither alone is the check. The set equality is what makes
/// a name added to `UNCONSTRUCTIBLE` without an entry here fail, so the
/// exclusion cannot be widened by editing one list; the emptiness is what the
/// entry proves, since a type declaring a status the sweep never drives is a
/// response nothing in the suite ever reads.
#[test]
fn every_unconstructible_short_circuit_declares_no_status() {
    assert_eq!(
        UNCONSTRUCTIBLE_STATUSES
            .iter()
            .map(|(name, _)| *name)
            .collect::<BTreeSet<_>>(),
        UNCONSTRUCTIBLE.iter().copied().collect::<BTreeSet<_>>(),
        "a name is excluded from the sweep with nothing here to prove it earned"
    );

    for (name, statuses) in UNCONSTRUCTIBLE_STATUSES {
        assert!(
            statuses.is_empty(),
            "`{name}` is excluded from the sweep and declares {statuses:?}, which no test reads"
        );
    }
}

/// Every short circuit Kynos ships, named against the set the sweep drives.
///
/// The same argument as the two sets above, one trait further out: without it a
/// ninth short circuit is a type the sweep silently does not reach. The walk is
/// over the whole crate rather than `src/middleware/`, because `Infallible`'s
/// implementation is in `src/response/mod.rs`.
#[test]
fn every_short_circuit_kynos_ships_is_accounted_for() {
    let declared = impls_of("ShortCircuit");

    assert_eq!(
        declared,
        SHORT_CIRCUITS
            .iter()
            .map(|name| (*name).to_owned())
            .collect::<BTreeSet<_>>(),
        "`src/` implements `ShortCircuit` for a different set than the sweep accounts for; a \
         short circuit added without a case is one whose description nothing reads"
    );
}

/// The type names `crates/kynos/src` implements `trait_name` for, read off disk.
///
/// Line-oriented rather than the `> Marker for ` substring `implementors_of`
/// uses, for two reasons that marker cannot cover: `ShortCircuit` takes no
/// generic argument to close the match on, and `middleware/limits.rs` writes
/// `impl ShortCircuit for TookTooLong` inside a doc example, which a bare
/// substring would count as a shipped implementation. Two conditions rather
/// than one: the line's code *begins* with `impl`, which excludes the `/// # `
/// that doc example carries, and the trait name begins a path segment, which
/// admits `impl crate::response::ShortCircuit for NotAcceptable` and refuses a
/// `MyShortCircuit` that merely ends in the name.
///
/// # What it requires of an implementation
///
/// `impl`, the trait name and `for` must fall on one line. Every implementation
/// in the tree does, and `ShortCircuit` takes no generic argument to push one
/// over the width.
///
/// # The two shapes it cannot see
///
/// Both go quiet rather than red, and the sweep derives what it must drive
/// from this set, so a miss here is a miss there too.
///
/// The first is a head rustfmt wrapped after a generic list, which puts
/// `impl<T: Bound>` and `ShortCircuit for Foo<T>` on separate lines with
/// neither carrying both. The prefix was `impl ` with a space until now, which
/// made this wider than its own description: `impl<const CODE: u16>` has no
/// space in that position either, so *every* generic head was invisible,
/// wrapped or not, and `response/status.rs` already writes that shape for
/// `Redirect<CODE>`. Dropping the space leaves only the genuinely wrapped head
/// missed, and an implementation added later keeps its head on one line or
/// this learns to join continuations first.
///
/// The second is a derive, and it is not closable here. `ApiError` emits
/// `impl ... ShortCircuit for` from `kynos-macros`, so the text exists in the
/// macro crate and the implementation exists in whichever crate wrote the
/// type. A walk over `crates/kynos/src` sees neither. That bounds what this
/// set claims rather than leaving a gap in it: the claim is over the short
/// circuits Kynos *ships*, and a derived one belongs to the application. What
/// the sweep would say about it is said instead by the conformance matrix,
/// which holds an application's own short circuit on a live exchange.
fn impls_of(trait_name: &str) -> BTreeSet<String> {
    let mut sources = Vec::new();
    collect_sources(
        std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src")),
        &mut sources,
    );
    assert!(!sources.is_empty(), "no sources found under `src/`");

    names_implementing(trait_name, &sources)
}

/// The scan itself, over source it is handed rather than source it reads.
///
/// Separate from [`impls_of`] so that the two conditions above can be held
/// against text: what the walk finds on disk is whatever the tree happens to
/// contain today, so a condition can only be *asserted* over source chosen to
/// exercise it.
fn names_implementing(trait_name: &str, sources: &[String]) -> BTreeSet<String> {
    let marker = format!("{trait_name} for ");

    sources
        .iter()
        .flat_map(|source| source.lines())
        .filter_map(|line| {
            let code = line.trim_start();
            if !code.starts_with("impl") {
                return None;
            }
            let at = code.match_indices(&marker).map(|(at, _)| at).find(|at| {
                code[..*at]
                    .chars()
                    .next_back()
                    .is_none_or(|character| !character.is_alphanumeric() && character != '_')
            })?;
            Some(
                code[at + marker.len()..]
                    .chars()
                    .take_while(|character| character.is_alphanumeric() || *character == '_')
                    .collect::<String>(),
            )
        })
        .collect()
}

/// Each condition the scan applies, held against a line written to break it.
///
/// The set the sweep drives is derived from what this finds, so a condition
/// that stops holding is a short circuit that stops being swept -- silently,
/// because the walk would simply return a smaller set and every assertion over
/// it would still pass. What the tree contains cannot say this: it holds no
/// `MyShortCircuit`, and dropping either condition leaves the walk's own result
/// unchanged today.
///
/// One line per claim the documentation above makes. The generic head is the
/// shape `impl` without a trailing space exists for; the qualified path is the
/// spelling `middleware/compression/mod.rs` uses; `MyShortCircuit` is a name
/// ending in the trait's that implements something else; and the doc-example
/// line
/// is the one `middleware/limits.rs` really carries.
#[test]
fn the_scan_reads_a_generic_head_and_anchors_the_trait_name() {
    let sources = [
        "impl<const CODE: u16> ShortCircuit for Redirected<CODE> {".to_owned(),
        "impl ShortCircuit for Plain {".to_owned(),
        "    impl crate::response::ShortCircuit for Qualified {".to_owned(),
        "impl MyShortCircuit for NotOurs {".to_owned(),
        "/// # impl ShortCircuit for InADocExample {".to_owned(),
    ];

    assert_eq!(
        names_implementing("ShortCircuit", &sources),
        ["Plain", "Qualified", "Redirected"]
            .into_iter()
            .map(str::to_owned)
            .collect::<BTreeSet<_>>()
    );
}

/// One short circuit's wire response, beside the description it declared.
struct Case {
    name: &'static str,
    /// The statuses the implementation claims, so the sweep can assert it drove
    /// every one of them rather than whichever variant it happened to name.
    claimed: &'static [u16],
    /// The status this value answered with. Read from the wire rather than from
    /// `STATUSES`, because a `STATUSES` list may hold several and one value
    /// answers with exactly one — `Undecodable` declares three and needs three
    /// variants driven to reach them.
    status: u16,
    media_type: Option<String>,
    body_len: usize,
    declared: kynos::openapi::Responses,
}

/// Drives one short circuit and records both halves of its promise.
async fn case<S: kynos::response::ShortCircuit>(
    registry: &mut kynos::schema::registry::Registry,
    value: S,
) -> Case {
    use http_body_util::BodyExt;

    // `responses` and `into_response` resolve through `ShortCircuit`'s
    // supertraits, so neither trait is imported here.
    let declared = S::responses(registry);
    let (parts, body) = value.into_response().into_parts();

    let media_type = parts
        .headers
        .get(kynos::http::header::CONTENT_TYPE)
        .and_then(|value| value.to_str().ok())
        .map(|value| {
            let (media_type, _) = value.split_once(';').unwrap_or((value, ""));
            media_type.trim().to_ascii_lowercase()
        });

    let body = body.collect().await.expect("a readable body").to_bytes();

    Case {
        // The generic argument goes first, then the path. `type_name` of a
        // generic short circuit is `..::RateLimited<..::Throttled>`, whose last
        // `::` segment is `Throttled>` -- a name that appears in no source
        // file, so the sweep would report a short circuit nobody wrote. The
        // source-text half already stops at `<`, and this is the same rule on
        // the other side of the comparison.
        name: std::any::type_name::<S>()
            .split('<')
            .next()
            .expect("a type name is not empty")
            .rsplit("::")
            .next()
            .expect("a type name has a last segment"),
        claimed: S::STATUSES,
        status: parts.status.as_u16(),
        media_type,
        body_len: body.len(),
        declared,
    }
}

/// One value per short circuit this build compiled, driven against one registry.
///
/// Values rather than types, because a description is a claim about what the
/// wire carries and only a value produces one. `Undecodable` appears three
/// times: it declares three statuses and one value answers with one of them.
async fn every_case() -> Vec<Case> {
    use std::time::Duration;

    use kynos::middleware::{
        csrf::CrossSite,
        limits::{AtCapacity, BodySizeExceeded, TimedOut},
        rate_limit::refusal::{RateLimited, RateLimitedFields},
    };

    let registry = &mut kynos::schema::registry::Registry::new();
    let mut cases = Vec::new();

    cases.push(case(registry, BodySizeExceeded::<()>::new(64)).await);
    cases.push(case(registry, TimedOut::<()>::new(Duration::from_secs(1))).await);
    cases.push(
        case(
            registry,
            AtCapacity::<()>::new(Some(Duration::from_secs(1))),
        )
        .await,
    );
    cases.push(case(registry, CrossSite::<()>::new()).await);
    cases.push(case(registry, RateLimited::<()>::new(Duration::from_secs(1), 10)).await);
    cases.push(
        case(
            registry,
            RateLimitedFields::<()>::new(Duration::from_secs(1), Vec::new(), Vec::new()),
        )
        .await,
    );

    #[cfg(feature = "compression")]
    {
        use kynos::middleware::{compression::NotAcceptable, decompression::Undecodable};

        cases.push(case(registry, NotAcceptable::<()>::new()).await);
        cases.push(case(registry, Undecodable::<(), (), ()>::unsupported_coding()).await);
        cases.push(case(registry, Undecodable::<(), (), ()>::malformed()).await);
        cases.push(case(registry, Undecodable::<(), (), ()>::too_large(64)).await);
    }

    #[cfg(feature = "cache")]
    {
        use kynos::{http::HeaderMap, middleware::conditional::NotModified};

        cases.push(case(registry, NotModified::from_headers(&HeaderMap::new())).await);
    }

    cases
}

/// The names the sweep must drive, derived from `SHORT_CIRCUITS` rather than
/// transcribed a second time — so a ninth implementation fails both tests.
///
/// A `cfg` per element rather than a second list, so the set stays derived at
/// every feature combination.
fn expected_names() -> BTreeSet<&'static str> {
    /// Whichever members this build did not compile.
    const ABSENT: &[&str] = &[
        #[cfg(not(feature = "compression"))]
        "NotAcceptable",
        #[cfg(not(feature = "compression"))]
        "Undecodable",
        #[cfg(not(feature = "cache"))]
        "NotModified",
    ];

    SHORT_CIRCUITS
        .iter()
        .copied()
        .filter(|name| !UNCONSTRUCTIBLE.contains(name) && !ABSENT.contains(name))
        .collect()
}

/// Every short circuit's *described* response declares the body its *wire*
/// response sends.
///
/// The defect issue #104 reported, asserted over the whole set rather than at
/// the site that was noticed: eight of the ten implementations put a problem
/// document on the wire under a response declaring no content, and
/// `assert_conformance` read an undeclared content as "nothing to check".
///
/// Both directions. `NotModified` sends no body and must go on declaring none,
/// so this is not "every short circuit declares content" — it is "the
/// declaration and the exchange agree".
#[tokio::test]
async fn every_short_circuit_declares_the_content_it_sends() {
    let cases = every_case().await;

    let driven: BTreeSet<&str> = cases.iter().map(|case| case.name).collect();
    assert_eq!(
        driven,
        expected_names(),
        "a short circuit the sweep does not drive"
    );

    // Every status an implementation claims has a value behind it, so a variant
    // added to a multi-status short circuit cannot go undriven.
    for name in &driven {
        let claimed: BTreeSet<u16> = cases
            .iter()
            .find(|case| &case.name == name)
            .expect("a driven case")
            .claimed
            .iter()
            .copied()
            .collect();
        let reached: BTreeSet<u16> = cases
            .iter()
            .filter(|case| &case.name == name)
            .map(|case| case.status)
            .collect();
        assert_eq!(
            reached, claimed,
            "`{name}` declares statuses the sweep does not drive a value to"
        );
    }

    for case in &cases {
        let key = kynos::openapi::StatusPattern::Code(case.status).to_string();
        let Some(kynos::openapi::RefOr::Item(declared)) = case.declared.responses.get(&key) else {
            panic!(
                "`{}` answers {} and its description declares no such response",
                case.name, case.status
            );
        };

        if let Some(media_type) = case.media_type.as_deref() {
            assert!(
                declared.content.contains_key(media_type),
                "`{}`'s {} sends a `{media_type}` body the description does not declare",
                case.name,
                case.status
            );
        } else {
            assert!(
                case.body_len == 0,
                "`{}`'s {} sends {} bytes with no `Content-Type`",
                case.name,
                case.status,
                case.body_len
            );
            assert!(
                declared.content.is_empty(),
                "`{}`'s {} declares content it does not send",
                case.name,
                case.status
            );
        }
    }
}

// --- What a marker must not cost the type that names it -------------------
//
// The four implementations every parameterised refusal writes out by hand, and
// the auto traits its `PhantomData<fn() -> T>` protects. Held here rather than
// per module because it is one rule over a set: a `#[derive]` anywhere in that
// set would bound the marker and take the implementation away from every
// application whose marker is only a name.

/// A marker that is deliberately neither `Send` nor `Sync`.
///
/// A raw pointer is the cheapest way to be neither. It is still `'static`, so
/// it satisfies `ProblemType` and the only thing under test is whether the
/// refusal's auto traits followed it.
struct Unsendable(std::marker::PhantomData<*const ()>);

impl kynos::error::problem::ProblemType for Unsendable {
    const TYPE_URI: Option<&'static str> = Some("https://errors.example.com/unsendable");
}

/// A marker deriving nothing at all, which is what an application writes.
struct Bare;

impl kynos::error::problem::ProblemType for Bare {
    const TYPE_URI: Option<&'static str> = Some("https://errors.example.com/bare");
}

/// Witnesses that a refusal crosses a task boundary whatever names its type.
fn assert_send_sync<T: Send + Sync>() {}

/// Witnesses the four implementations a refusal writes out.
fn assert_refusal_traits<T: Clone + std::fmt::Debug + Eq>() {}

/// Witnesses the two an interceptor writes out.
fn assert_clone_and_debug<T: Clone + std::fmt::Debug>() {}

/// A refusal is `Send` and `Sync` whatever marker names its problem type.
///
/// The field is `PhantomData<fn() -> T>` rather than `PhantomData<T>` for this
/// reason and no other: a `PhantomData<T>` inherits `T`'s auto traits, and a
/// refusal that is not `Send` cannot be returned from an interceptor at all —
/// a bound failure at every mount site, from a marker the application thought
/// was only a name.
#[test]
fn a_refusal_is_send_and_sync_whatever_marker_names_it() {
    use kynos::middleware::{
        csrf::CrossSite,
        limits::{AtCapacity, BodySizeExceeded, TimedOut},
    };

    assert_send_sync::<BodySizeExceeded<Unsendable>>();
    assert_send_sync::<TimedOut<Unsendable>>();
    assert_send_sync::<AtCapacity<Unsendable>>();
    assert_send_sync::<CrossSite<Unsendable>>();

    #[cfg(feature = "compression")]
    {
        use kynos::middleware::{compression::NotAcceptable, decompression::Undecodable};

        assert_send_sync::<NotAcceptable<Unsendable>>();
        assert_send_sync::<Undecodable<Unsendable, Unsendable, Unsendable>>();
    }
}

/// A refusal and its interceptor keep their implementations whatever names the
/// problem type.
///
/// `Bare` derives nothing, so these calls are the whole test: a `#[derive]` on
/// any of these types would bound the marker and refuse them.
#[test]
fn naming_a_problem_type_costs_the_marker_no_derives() {
    use kynos::middleware::{
        csrf::{CrossSite, Csrf},
        limits::{AtCapacity, BodySize, BodySizeExceeded, Concurrency, TimedOut},
    };

    assert_refusal_traits::<BodySizeExceeded<Bare>>();
    assert_refusal_traits::<TimedOut<Bare>>();
    assert_refusal_traits::<AtCapacity<Bare>>();
    assert_refusal_traits::<CrossSite<Bare>>();

    assert_clone_and_debug::<BodySize<Bare>>();
    assert_clone_and_debug::<Concurrency<Bare>>();
    assert_clone_and_debug::<Csrf<Bare>>();

    #[cfg(feature = "compression")]
    {
        use kynos::middleware::{
            compression::{Compression, NotAcceptable},
            decompression::{Decompression, Undecodable},
        };

        assert_refusal_traits::<NotAcceptable<Bare>>();
        assert_refusal_traits::<Undecodable<Bare, Bare, Bare>>();

        assert_clone_and_debug::<Compression<Bare>>();
        assert_clone_and_debug::<Decompression<Bare, Bare, Bare>>();
    }
}