feat(macros): reject empty tool_router (#1233)

Closes #1174

Claude-Session: https://claude.ai/code/session_017tyTJfwGpE8fidbZSRi6uU
This commit is contained in:
Dale Seo 2026-09-08 19:04:40 -04:00 • committed by GitHub
commit 7d14f3a8a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -573,3 +573,47 @@ fn test_manual_get_info_not_overridden() {
"manual resources should be preserved"
);
}
/// Server whose tools come from a `macro_rules!` helper wrapping the whole annotated impl.
#[derive(Debug, Clone)]
struct MacroGeneratedServer;
macro_rules! define_tools {
($($name:ident => $description:literal),* $(,)?) => {
#[tool_router]
impl MacroGeneratedServer {
$(
#[tool(description = $description)]
async fn $name(&self) -> String {
stringify!($name).to_owned()
}
)*
}
};
}
define_tools!(probe => "what a capability would own");
#[test]
fn test_macro_rules_around_the_impl_registers_tools() {
let tools = MacroGeneratedServer::tool_router().list_all();
assert_eq!(tools.len(), 1);
assert_eq!(tools[0].name, "probe");
assert_eq!(
tools[0].description.as_deref(),
Some("what a capability would own")
);
}
/// Server that opts in to a router with no tools.
#[derive(Debug, Clone)]
struct EmptyRouterServer;
#[tool_router(allow_empty)]
impl EmptyRouterServer {}
#[test]
fn test_allow_empty_builds_a_router_without_tools() {
assert!(EmptyRouterServer::tool_router().list_all().is_empty());
}