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
//! Serving one resource in three languages.
//!
//! ```text
//! cargo run -p kynos --example localization
//! ```
//!
//! ```text
//! curl -i localhost:3000/greeting -H 'Accept-Language: fr-CA, en;q=0.5'
//! curl -i localhost:3000/greeting -H 'Accept-Language: ja'
//! ```
//!
//! Four things are worth noticing, and the first is the one that surprises
//! people coming from `Accept`:
//!
//! * **`Accept-Language` *is* a parameter.** OpenAPI names exactly three header
//! fields whose parameter definition shall be ignored — `Accept`,
//! `Content-Type` and `Authorization` — and this is not one of them. So
//! `negotiation.rs` is right that declaring `Accept` is a claim no consumer
//! will honour, and this file is right to declare the opposite. The two axes
//! look alike and their descriptions are shaped differently for a reason the
//! specification gives.
//!
//! * **The offer is stated on the response, not the request.** The parameter's
//! schema is an unconstrained string, because the value is a *priority list*:
//! `da, en-gb;q=0.8, en;q=0.7` is not a member of any set of tags. The offer
//! is enumerated on `Content-Language`, where it is true — that field carries
//! one tag, and `Localized` has no public constructor, so the only value that
//! can reach the wire is one the negotiation chose.
//!
//! * **Nothing here can fail, and the description says so.** There is no 406
//! and no 400: a client asking for a language nobody offers is served the
//! default, and a range the parser cannot read is dropped while the rest of
//! the field still counts. Adding language negotiation to an operation adds
//! no status to it. What makes that honest rather than silent is
//! `Content-Language`, which is `required` — a client that cannot use the
//! fallback can always see that it got one.
//!
//! * **The catalogue is yours.** Kynos negotiates the language and states which
//! one it chose; the strings are the application's, and the `HashMap` below
//! could as easily be Fluent, gettext, or files on disk. Kynos ships no
//! translations and no message-format model — see `docs/architecture.md`'s
//! third invariant for why that is a refusal rather than an omission.
//!
//! The offer is written down rather than discovered, because
//! `Describe::describe` takes no `self`: a set held in a value cannot reach the
//! description at all.
use ;
use ;
/// The languages this service answers in.
///
/// The first is the default: it is what a request carrying no preference gets,
/// and what one whose preferences match nothing gets. Every tag here is checked
/// for RFC 5646 well-formedness while the program compiles, so `en_GB` — the
/// POSIX spelling, with an underscore — would not build.
;
/// The greetings, keyed by the tags the offer names.
///
/// An ordinary map built at startup. Nothing about it is Kynos's: what the
/// framework supplied is the `&'static str` used to index it.
/// A greeting in whichever language the client and the service agree on.
///
/// The closure receives the chosen tag, which is the shape difference from
/// `Accept::respond_with`: a handler needs the language *before* it builds
/// anything, because the language is what indexes the catalogue.
async
/// A program generic over what it offers.
///
/// `Languages` is public and unsealed, unlike `Representations`: the offerable
/// codecs are Kynos's to know, and a catalogue is not.
async