mod common;
use common::{count_occurrences, library_sources};
use std::collections::BTreeMap;
use std::path::PathBuf;
#[test]
fn no_public_type_is_orphaned() {
let sources = library_sources();
let mut declared: BTreeMap<String, PathBuf> = BTreeMap::new();
for (path, text) in &sources {
for line in text.lines() {
let line = line.trim_start();
for prefix in ["pub struct ", "pub enum "] {
if let Some(rest) = line.strip_prefix(prefix) {
let name: String = rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
if !name.is_empty() {
declared.insert(name, path.clone());
}
}
}
}
}
assert!(
declared.len() > 300,
"expected 300+ public types, found {} — the scan is not seeing the sources",
declared.len()
);
let mut orphans = Vec::new();
for (name, home) in &declared {
let mut references = 0;
for (path, text) in &sources {
references += count_occurrences(text, name);
if path == home {
let declarations = text
.lines()
.filter(|line| {
let line = line.trim_start();
["pub struct ", "pub enum "].iter().any(|prefix| {
line.strip_prefix(prefix)
.and_then(|rest| rest.strip_prefix(name.as_str()))
.is_some_and(|after| {
!after.starts_with(|c: char| c.is_alphanumeric() || c == '_')
})
})
})
.count();
references = references.saturating_sub(declarations);
}
}
if references <= 1 {
let file = home.file_name().unwrap_or_default().to_string_lossy();
orphans.push(format!(
" {name} ({file}) — declared and exported, used nowhere"
));
}
}
assert!(
orphans.is_empty(),
"{} public type(s) are wired to nothing. Either something that should \
reference them points at the wrong type, or they are dead and should be \
removed:\n{}",
orphans.len(),
orphans.join("\n")
);
}
const UNTYPED_ALLOWLIST: &[(&str, &str, &str)] = &[
(
"methods/games.rs",
"impl_into_future!(SetGameScore, serde_json::Value, \"setGameScore\");",
"setGameScore returns `Message` for a chat message and `True` for an \
inline one. The crate has no union type for that, and inventing one is \
a design decision rather than a wiring task.",
),
(
"methods/sending.rs",
"extra: serde_json::Value,",
"media_json_body is a private helper that merges caller-supplied extra \
fields into a JSON body. A Value is the correct type here.",
),
(
"methods/sending.rs",
") -> serde_json::Value {",
"The return of that same private helper.",
),
];
#[test]
fn no_untyped_escape_hatches_outside_the_allowlist() {
let mut found = Vec::new();
for (path, text) in library_sources() {
let display = path
.components()
.rev()
.take(2)
.collect::<Vec<_>>()
.into_iter()
.rev()
.map(|c| c.as_os_str().to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join("/");
for (number, line) in text.lines().enumerate() {
let trimmed = line.trim();
if !trimmed.contains("serde_json::Value") {
continue;
}
if trimmed.starts_with("//") {
continue;
}
if ["serde_json::Value::", "to_value", "from_value", "json!"]
.iter()
.any(|marker| trimmed.contains(marker))
{
continue;
}
let allowed = UNTYPED_ALLOWLIST
.iter()
.any(|(file, snippet, _)| display.ends_with(file) && trimmed == *snippet);
if !allowed {
found.push(format!(" {display}:{}: {trimmed}", number + 1));
}
}
}
assert!(
found.is_empty(),
"{} untyped `serde_json::Value` site(s) not in the allowlist. Model the \
real type, or add an entry with a reason:\n{}",
found.len(),
found.join("\n")
);
}
#[test]
fn every_allowlist_entry_still_applies() {
let sources = library_sources();
let mut stale = Vec::new();
for (file, snippet, _reason) in UNTYPED_ALLOWLIST {
let matched = sources.iter().any(|(path, text)| {
path.to_string_lossy().ends_with(file) && text.lines().any(|l| l.trim() == *snippet)
});
if !matched {
stale.push(format!(" {file}: {snippet}"));
}
}
assert!(
stale.is_empty(),
"{} allowlist entr(ies) no longer match anything — remove them:\n{}",
stale.len(),
stale.join("\n")
);
}