use std::{fs, path::PathBuf};
use syn::{spanned::Spanned, ImplItem, Item, Visibility};
const SKIP_MARKER: &str = "no #[steam_endpoint]";
fn services_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src").join("services")
}
#[test]
fn every_pub_async_fn_is_annotated_or_marked_delegate() {
let dir = services_dir();
let entries = fs::read_dir(&dir).expect("services dir readable");
let mut failures: Vec<String> = Vec::new();
let mut files_checked = 0usize;
let mut fns_checked = 0usize;
for entry in entries {
let path = entry.expect("dir entry").path();
if path.extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
if !path.is_file() {
continue;
}
files_checked += 1;
let src = fs::read_to_string(&path).expect("read service file");
let lines: Vec<&str> = src.lines().collect();
let file_ast = syn::parse_file(&src).unwrap_or_else(|e| panic!("parse {}: {e}", path.display()));
let rel = path.file_name().and_then(|s| s.to_str()).unwrap_or("?");
for item in &file_ast.items {
let Item::Impl(item_impl) = item else { continue };
for impl_item in &item_impl.items {
let ImplItem::Fn(fn_item) = impl_item else { continue };
let is_pub = matches!(fn_item.vis, Visibility::Public(_));
let is_async = fn_item.sig.asyncness.is_some();
if !(is_pub && is_async) {
continue;
}
fns_checked += 1;
let has_endpoint_attr = fn_item
.attrs
.iter()
.any(|a| a.path().is_ident("steam_endpoint"));
if has_endpoint_attr {
continue;
}
let fn_line_1based = fn_item.sig.fn_token.span().start().line;
if fn_line_1based == 0 {
failures.push(format!(
"services/{rel}: fn `{}` — span info missing (proc-macro2 span-locations feature?)",
fn_item.sig.ident
));
continue;
}
let fn_line_idx = fn_line_1based - 1;
let look_back_window = fn_line_idx.saturating_sub(8)..fn_line_idx;
let has_skip_comment = lines[look_back_window].iter().any(|line| {
let trimmed = line.trim_start();
trimmed.starts_with("//") && trimmed.contains(SKIP_MARKER)
});
if !has_skip_comment {
failures.push(format!(
"services/{rel}:{fn_line_1based} fn `{}` — missing #[steam_endpoint(...)] and no `// ...{SKIP_MARKER}` comment in the 8 lines above",
fn_item.sig.ident,
));
}
}
}
}
assert!(
files_checked > 0,
"no .rs files found in {} — guard would silently pass",
services_dir().display(),
);
assert!(
fns_checked >= 100,
"only {fns_checked} pub async fn discovered — parser probably regressed",
);
if !failures.is_empty() {
let count = failures.len();
let body = failures.join("\n ");
panic!("{count} pub async fn(s) missing endpoint metadata:\n {body}");
}
}