use std::path::Path;
const URL_METHODS: &[&str] = &[
"get",
"get_with_query",
"post",
"put",
"patch",
"delete_empty",
"post_empty",
"get_raw",
"get_sse_stream",
"upload_file",
"download_file",
];
fn flatten(src: &str) -> String {
src.chars().filter(|c| !c.is_whitespace()).collect()
}
fn rust_sources(dir: &Path, out: &mut Vec<std::path::PathBuf>) {
for entry in std::fs::read_dir(dir).expect("read_dir") {
let path = entry.expect("dir entry").path();
if path.is_dir() {
rust_sources(&path, out);
} else if path.extension().is_some_and(|e| e == "rs") {
out.push(path);
}
}
}
#[test]
fn client_call_sites_are_not_api_prefixed() {
let src_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut files = Vec::new();
rust_sources(&src_dir, &mut files);
assert!(!files.is_empty(), "found no sources under {src_dir:?}");
let mut offenders = Vec::new();
for file in &files {
let flat = flatten(&std::fs::read_to_string(file).expect("read source"));
for method in URL_METHODS {
for prefix in [
format!(".{method}(\"/api/v1"),
format!(".{method}(&format!(\"/api/v1"),
format!(".{method}(format!(\"/api/v1"),
] {
if flat.contains(&prefix) {
offenders.push(format!(
"{}: .{method}() called with an /api/v1-prefixed path",
file.strip_prefix(env!("CARGO_MANIFEST_DIR"))
.unwrap_or(file)
.display()
));
}
}
}
}
assert!(
offenders.is_empty(),
"Client::url() already prepends /api/v1 — these call sites double it:\n {}\n\n\
Pass an API-relative path instead, e.g. \"/registry/tokens\".",
offenders.join("\n ")
);
}