#[test]
fn unguarded_crud_mounts_remain_feature_gated() {
let lib = include_str!("../src/lib.rs");
let routes = include_str!("../src/routes/mod.rs");
let marker = "feature = \"unguarded\"";
assert_eq!(
lib.matches(marker).count(),
2,
"src/lib.rs must keep exactly 2 `unguarded` cfg gates (on all_crud_routes and routes). \
If regen removed them, re-apply #[cfg(any(test, feature = \"unguarded\"))] above each \
and ensure src/lib.rs is listed under user_owned in metaphor.codegen.yaml."
);
assert_eq!(
routes.matches(marker).count(),
4,
"src/routes/mod.rs must keep exactly 4 `unguarded` cfg gates (on create_stateless_routes, \
get_routes, create_combined_routes, get_routes_with_state). If regen removed them, \
re-apply the attribute above each and ensure src/routes/mod.rs is user_owned."
);
}
#[test]
fn every_feature_that_needs_a_dependency_declares_it() {
let manifest = include_str!("../Cargo.toml");
let uses_utoipa = source_files_mentioning("utoipa::");
if uses_utoipa.is_empty() {
return;
}
assert!(
manifest.lines().any(|l| l.trim_start().starts_with("utoipa")),
"{} source file(s) import utoipa behind the `openapi` feature (first: {}), \
so Cargo.toml must declare the dependency. Without it `cargo check \
--all-features` fails on an unresolved import.",
uses_utoipa.len(),
uses_utoipa[0]
);
if !source_files_mentioning("Decimal").is_empty() {
assert!(
manifest
.lines()
.filter(|l| l.trim_start().starts_with("utoipa"))
.any(|l| l.contains("decimal")),
"DTOs expose rust_decimal values, so the utoipa dependency must enable \
its `decimal` feature or the derive cannot describe them."
);
}
}
fn source_files_mentioning(needle: &str) -> Vec<String> {
fn walk(dir: &std::path::Path, needle: &str, out: &mut Vec<String>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
walk(&path, needle, out);
} else if path.extension().is_some_and(|e| e == "rs")
&& std::fs::read_to_string(&path)
.map(|s| s.contains(needle))
.unwrap_or(false)
{
out.push(path.display().to_string());
}
}
}
let mut out = Vec::new();
walk(std::path::Path::new("src"), needle, &mut out);
out.sort();
out
}