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
//! Drift-guard for the spec/field-enum label convention (issue #204),
//! introduced in `transmux` by issue #580.
//!
//! Scans this crate's `src/` for every `pub enum`, subtracts a documented
//! skip-list, and fails if any remaining enum has neither
//! `broadcast_common::impl_spec_display!(Name)` nor a hand-written `Display` impl.
//!
//! Because the project-wide `Display` impl delegates to an inherent
//! `name() -> &'static str`, a present `Display` transitively guarantees
//! `name()` exists (it would not compile otherwise) — so this single coverage
//! check enforces the whole convention and catches the one thing the compiler
//! cannot: a brand-new `pub enum` that nobody labelled.
use std::collections::BTreeSet;
use std::fs;
use std::path::Path;
/// Enums that are intentionally **not** spec/field labels. Each is one of:
/// a structured error; a dispatch/tag enum whose variants wrap full
/// structured payloads (not a flat spec code); a `clap` CLI argument enum; or
/// a data-carrying ADT whose variants hold arbitrary payloads (a static label
/// would be lossy and add nothing — use the typed variant instead).
const SKIP: &[&str] = &[
// errors (Display comes from `thiserror`'s derive or a hand-written
// human-readable message, not a spec-token label)
"Error",
"FlvError",
"RtmpError",
"CliError",
// `dash_parse::DashParseError` (issue #758 T1): a structured parse error,
// labelled via a hand-written `Display` impl (matching `FlvError`), not
// the spec-token convention — already satisfied by that `Display` impl,
// listed here for the same documentation reason `FlvError`/`RtmpError` are.
"DashParseError",
// `smooth_parse::SmoothParseError` (issue #759 T1): a structured parse
// error, labelled via a hand-written `Display` impl mirroring
// `DashParseError`, not the spec-token convention — already satisfied by
// that `Display` impl, listed here for the same documentation reason.
"SmoothParseError",
// clap CLI argument enum (labels are the `--format` strings, owned by
// `clap::ValueEnum`, not this convention)
"FormatArg",
// data-carrying ADTs: every variant wraps a distinct structured payload
// (a full sample-entry/box/config/value type), so a flat label would be
// lossy — the typed variant *is* the label.
"CodecConfig",
"DemuxEvent",
// `rtp_stream::RtpLossEvent` (issue #779): a data-carrying ADT, same
// shape as `DemuxEvent` above — each variant wraps distinct structured
// fields (track/ssrc/sequence numbers), not a flat spec-defined code.
"RtpLossEvent",
// `LlHlsSegmenter`'s `Stage::Out` (media plane step 2e-2): a dispatch
// enum wrapping two full structured payloads (`PartInfo`/`SegmentInfo`),
// same shape as `DemuxEvent` above — not a spec-defined flat code.
"LlHlsStageOutput",
"Output",
"SampleEntryVariant",
"StblChild",
"ProtocolControl",
"AmfValue",
"SgpdEntry",
// `cenc_encrypt` (#564) config enums: `IvGen` carries either a scalar
// counter base or a full caller-supplied IV list (data-carrying ADT, like
// `CodecConfig` above); `SubsamplePolicy` is a plain behaviour switch, not
// a spec-defined wire code, so a `name()`/`Display` label would be
// fabricated rather than transcribed from a spec table.
"IvGen",
"SubsamplePolicy",
];
fn read_rs(dir: &Path, out: &mut Vec<String>) {
for entry in fs::read_dir(dir).expect("read src dir") {
let path = entry.expect("dir entry").path();
if path.is_dir() {
read_rs(&path, out);
} else if path.extension().is_some_and(|x| x == "rs") {
out.push(fs::read_to_string(&path).expect("read .rs"));
}
}
}
/// True if `name` appears after `prefix` with an identifier boundary, i.e. the
/// match is the whole enum name and not a longer one sharing the prefix.
fn has_impl(all: &str, prefix: &str, name: &str) -> bool {
let needle = format!("{prefix}{name}");
let is_boundary =
|rest: &str| !matches!(rest.chars().next(), Some(c) if c.is_alphanumeric() || c == '_');
let is_path_or_space =
|c: char| c.is_whitespace() || c == ':' || c.is_alphanumeric() || c == '_';
// Strip a leading `ident::` chain (`crate::`, `broadcast_common::`, …) so a
// re-exported or fully-qualified invocation still counts as reaching the
// needle from the very start of the line.
fn strip_path_qualifier(s: &str) -> &str {
let mut rest = s;
loop {
let ident_len = rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.count();
if ident_len == 0 {
break;
}
match rest[ident_len..].strip_prefix("::") {
Some(after) => rest = after,
None => break,
}
}
rest
}
for line in all.lines() {
let trimmed = line.trim_start();
// The invocation must be the first non-whitespace token on its line —
// a commented-out `// impl_spec_display!(...)` no longer satisfies
// this. A leading crate-path qualifier (`crate::`, `broadcast_common::`)
// is transparent to this check: it is still the first *statement*.
if let Some(rest) = strip_path_qualifier(trimmed).strip_prefix(&needle)
&& is_boundary(rest)
{
return true;
}
// A bare `Display for Name` needle (the generic fallback some crates
// use) also counts when reached from the `impl` keyword through
// nothing but a module-path qualifier: `impl ::core::fmt::Display for
// Name`, `impl std::fmt::Display for Name`, `impl fmt::Display for
// Name`, `impl Display for Name`.
if !needle.starts_with("impl")
&& let Some(after_impl) = trimmed.strip_prefix("impl")
&& let Some(pos) = after_impl.find(&needle)
{
let qualifier = &after_impl[..pos];
let rest = &after_impl[pos + needle.len()..];
if qualifier.chars().all(is_path_or_space) && is_boundary(rest) {
return true;
}
}
}
false
}
#[test]
fn every_public_spec_enum_has_a_display_impl() {
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut files = Vec::new();
read_rs(&src, &mut files);
let all = files.join("\n");
let mut enums = BTreeSet::new();
for line in all.lines() {
if let Some(rest) = line.trim_start().strip_prefix("pub enum ") {
let name: String = rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
if !name.is_empty() {
enums.insert(name);
}
}
}
let missing: Vec<_> = enums
.iter()
.filter(|e| !SKIP.contains(&e.as_str()))
.filter(|e| !has_impl(&all, "impl_spec_display!(", e) && !has_impl(&all, "Display for ", e))
.cloned()
.collect();
assert!(
missing.is_empty(),
"pub enum(s) missing a Display impl (issue #204 convention): {missing:?}\n\
Add `broadcast_common::impl_spec_display!(Name)` plus an inherent `name()`, \
or add the enum to SKIP if it is not a spec/field label."
);
}