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
//! Centralised `serde` implementations for the descriptor enums
//! (`feature = "serde"`).
//!
//! # Two laws, and the second one has legs
//!
//! **An open vocabulary is always its slug.** **A closed one splits on
//! the format**: its name where a human will read it, its code where
//! only a machine will. `Serializer::is_human_readable()` is what says
//! which, and the split is per-format rather than per-type — the same
//! value is `"native"` in JSON and a varint in postcard, and neither is
//! a fallback for the other.
//!
//! The reason the two laws differ is the escape arm, not taste. An open
//! vocabulary's `Other(SmolStr)` holds a name and *only* a name: there
//! is no code to fall back to for a value this build has never heard
//! of, so a numeric leg could not carry one and the slug is the only
//! honest wire at either end. A closed vocabulary has no such value —
//! every member has both spellings, so the format gets to choose, and a
//! binary format has no reason to pay for a string it cannot read
//! anyway.
//!
//! ## The open law — always the slug
//!
//! - **Every open vocabulary enum** — codecs, formats, the colour enums,
//! the pixel format, the frame coded enums — serializes as its
//! canonical `as_str()` slug: `VideoCodec::H264` ⇄ `"h264"`,
//! `color::Matrix::Bt709` ⇄ `"bt709"`, `Other("x265")` ⇄ `"x265"` (no
//! `{"Other": …}` wrapper). One extension idiom, one wire shape, every
//! format. Round-trip total wherever the `Other(SmolStr)` arm exists
//! (the `alloc` tier); at the no-alloc tier the same enums are closed,
//! so an unrecognised slug is a serde error rather than a
//! silently-invented value. Deserialization goes through the type's
//! `FromStr`, so it also reads the documented FFmpeg synonyms
//! (`"gray"` → `PixelFormat::Gray8`, `"unknown"` →
//! `color::Matrix::Unspecified`); serialization stays canonical, so a
//! synonym read off the wire is written back in the canonical
//! spelling.
//! - **`TrackDisposition`** is outside both laws: it is a bit set, not a
//! name vocabulary, so it serializes as its `u32` bits. The number
//! *is* the value and there is no name to spell — in any format.
//!
//! ## The closed law — the slug leg and the code leg
//!
//! **Strictly-closed coded enums (no `Other` arm)** —
//! [`crate::audio::BitRateMode`], [`crate::audio::ChannelOrder`] — take
//! both legs, and **both legs are strict**:
//!
//! | leg | shape | read side |
//! |---|---|---|
//! | `is_human_readable()` | the `as_str()` slug (`"cbr"`, `"native"`) | the type's `FromStr`; an unrecognised **name** is a serde error, and a *number* is refused outright — it is not a name |
//! | binary | the `to_u32()` code | `try_from_u32`; an out-of-range **code** is a serde error |
//!
//! Strict on both legs means the same thing on both: an input this
//! vocabulary cannot name is *refused*, never collapsed onto the default
//! variant the way `from_u32` would collapse it (`BitRateMode::from_u32(999)
//! == Cbr`, `ChannelOrder::from_u32(999) == Unspecified`). A corrupt or
//! out-of-range value must fail loudly rather than arrive looking like
//! valid data. The slug leg still folds ASCII case, because that is the
//! whole of the crate's folding and `"CBR"` is the same *name* as
//! `"cbr"` — folding a spelling is not inventing a value.
//!
//! [`crate::subtitle::TrackOrigin`] left this group in 0.5.0 when it
//! gained an `Other` arm: an open vocabulary has no closed code space to
//! police, and no code for its escape to carry, so it moved to the open
//! law above and stays there under both formats.
//!
//! `ChannelOrder`'s code space really is closed, which is what puts it
//! here: it mirrors FFmpeg's `AVChannelOrder`, four members with no
//! vendor range, so every integer outside `0..=3` is a corrupt read
//! rather than a name this build has not heard of.
//!
//! The plain data structs (`color::Info`, `frame::Dimensions`,
//! `audio::Tags`, `audio::ChannelSpec`,
//! `audio::ChannelLayoutDescription`, …) derive serde at their
//! definition site; the
//! validated structs (`capture::GeoLocation`, `audio::Fingerprint`,
//! `audio::CoverArt`, `frame::WhiteBalance`,
//! `frame::ColorCorrectionMatrix`) route deserialize through their
//! checking constructors there too. `lang::Language` carries a bespoke BCP-47
//! string impl in its module.
/// Implements `Serialize` / `Deserialize` for an *open* enum via its
/// canonical string slug (`as_str()` to serialize, [`FromStr`] to parse).
/// The `FromStr` impl is total (`Err = Infallible`) — unknown slugs ride
/// the enum's `Other` arm — but the deserializer surfaces any error as a
/// serde error for forward-compatibility.
///
/// [`FromStr`]: core::str::FromStr
/// Implements `Serialize` / `Deserialize` via a `u32` whose every value is
/// meaningful wire data — the bit-set case, where the number *is* the
/// value and there is no name to spell. `TrackDisposition` is the only
/// such type; name vocabularies use [`serde_via_str!`] instead.
/// Implements `Serialize` / `Deserialize` for a **strictly-closed**
/// FFmpeg-coded enum — one with no escape arm at all — with a leg per
/// format: the `as_str()` slug where `is_human_readable()`, the
/// `to_u32()` code where it is not.
///
/// Both legs are strict, and strict means the same thing on each: an
/// input this vocabulary cannot name is **refused**, never collapsed
/// onto the default variant the way `from_u32` would collapse it. The
/// slug leg refuses an unrecognised name through the type's own
/// `FromStr`; the code leg refuses an out-of-range code through
/// `try_from_u32`.
///
/// Two properties of the slug leg are load-bearing and easy to lose:
///
/// * **A number is not a name.** The visitor implements `visit_str` and
/// nothing else, so a JSON `1` reaches serde's default `visit_u64` and
/// comes back as an `invalid type` error rather than being read as a
/// code. The legs are alternatives, not a chain of fallbacks — a
/// human-readable document that carries a bare integer here is
/// malformed, not merely terse.
/// * **Case still folds.** `FromStr` goes through the crate's one ASCII
/// folding gate, so `"CBR"` and `"cbr"` are one value. Folding a
/// *spelling* is not inventing a *value*, which is the line strictness
/// is drawn on.
///
/// An open vocabulary does **not** get this treatment — see
/// [`serde_via_str!`] and the two laws in the module docs. Its
/// `Other(SmolStr)` holds a name with no code behind it, so a numeric
/// leg would have nothing to write.
// Both invocations are heap-tier — gated on
// `any(feature = "std", feature = "alloc")`. Under bare `--features serde`
// (no-alloc tier) they are cfg'd out and the macro is unused; the `allow`
// silences the resulting `unused_macros` lint, exactly as for `serde_via_str!`.
// ── The bit set: a number is its only faithful spelling ──
serde_via_code!;
// ── Name vocabularies available at every capability tier ──
// Open at the `alloc` tier (an unrecognised slug rides `Other`), closed at
// the no-alloc tier (it is a serde error) — one wire shape either way.
serde_via_str!;
serde_via_str!;
serde_via_str!;
serde_via_str!;
serde_via_str!;
serde_via_str!;
serde_via_str!;
serde_via_str!;
serde_via_str!;
serde_via_str!;
// ── The RAW / bayer vocabularies (behind the `bayer` feature) ──
// Closed: they name sensor layouts and demosaic algorithms, not an open
// space a backend extends, so an unrecognised slug is a serde error.
// `WhiteBalance` / `ColorCorrectionMatrix` are float structs and carry
// their own validating impls at their definition site.
serde_via_str!;
serde_via_str!;
serde_via_str!;
// ── Name vocabularies that need the allocator for their own payloads ──
serde_via_str!;
serde_via_str!;
serde_via_str!;
serde_via_str!;
serde_via_str!;
serde_via_str!;
serde_via_str!;
serde_via_str!;
serde_via_str!;
// ── Strictly-closed coded enums (no `Unknown` escape) ──
// Use `serde_via_slug_or_code!` — the slug where a human reads it, the
// code where only a machine does, and both legs strict: an unrecognised
// name or an out-of-range code is a serde error, never canonicalised to
// the default (which `from_u32` would do for
// `BitRateMode::from_u32(999) == Cbr` and
// `ChannelOrder::from_u32(999) == Unspecified`).
//
// No exceptions here: every member of this group takes both legs. A
// closed vocabulary pinned to one shape would be exactly the asymmetry
// the two-law split exists to remove.
serde_via_slug_or_code!;
serde_via_slug_or_code!;