fix(clippy): add doc comment for generated tool attr fn (#439)
This change makes the `tool` macro's output safe for the `missing_docs` lint, by emitting a doc comment for the generated `[tool]_tool_attr` function. This doc comment is emitted regardless of whether the tool function is public, for simplicity. We are building an MCP server using `rmcp` and discovered that the current crate was not compatible with the `#![deny(missing_docs)]` lint which we use everywhere. Tested by modifying the `test_tool_macros.rs` test to use `#![deny(missing_docs)]` and adding doc comments to all pub fns and structs in that file. None. Fixes #438. Co-authored-by: RobJellinghaus <rjellinghaus@live.com>
This commit is contained in:
parent
4ee2add899
commit
ddef4ce529
2 changed files with 26 additions and 7 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
use darling::{FromMeta, ast::NestedMeta};
|
use darling::{FromMeta, ast::NestedMeta};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{ToTokens, format_ident, quote};
|
use quote::{ToTokens, format_ident, quote};
|
||||||
use syn::{Expr, Ident, ImplItemFn, ReturnType};
|
use syn::{Expr, Ident, ImplItemFn, ReturnType, parse_quote};
|
||||||
|
|
||||||
use crate::common::{extract_doc_line, none_expr};
|
use crate::common::{extract_doc_line, none_expr};
|
||||||
|
|
||||||
|
|
@ -120,7 +120,10 @@ impl ResolvedToolAttribute {
|
||||||
} else {
|
} else {
|
||||||
quote! { None }
|
quote! { None }
|
||||||
};
|
};
|
||||||
|
let doc_comment = format!("Generated tool metadata function for {name}");
|
||||||
|
let doc_attr: syn::Attribute = parse_quote!(#[doc = #doc_comment]);
|
||||||
let tokens = quote! {
|
let tokens = quote! {
|
||||||
|
#doc_attr
|
||||||
pub fn #fn_ident() -> rmcp::model::Tool {
|
pub fn #fn_ident() -> rmcp::model::Tool {
|
||||||
rmcp::model::Tool {
|
rmcp::model::Tool {
|
||||||
name: #name.into(),
|
name: #name.into(),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
|
//! Test tool macros, including documentation for generated fns.
|
||||||
|
|
||||||
//cargo test --test test_tool_macros --features "client server"
|
//cargo test --test test_tool_macros --features "client server"
|
||||||
|
// Enforce that all generated code has sufficient docs to pass missing_docs lint
|
||||||
|
#![deny(missing_docs)]
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
@ -11,15 +15,19 @@ use rmcp::{
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Parameters for weather tool.
|
||||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct GetWeatherRequest {
|
pub struct GetWeatherRequest {
|
||||||
|
/// City of interest.
|
||||||
pub city: String,
|
pub city: String,
|
||||||
|
/// Date of interest.
|
||||||
pub date: String,
|
pub date: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tool_handler(router = self.tool_router)]
|
#[tool_handler(router = self.tool_router)]
|
||||||
impl ServerHandler for Server {}
|
impl ServerHandler for Server {}
|
||||||
|
|
||||||
|
/// Trivial stateless server.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
|
|
@ -27,6 +35,7 @@ pub struct Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
|
/// Create weather server.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
tool_router: Self::tool_router(),
|
tool_router: Self::tool_router(),
|
||||||
|
|
@ -53,8 +62,9 @@ impl Server {
|
||||||
async fn empty_param(&self) {}
|
async fn empty_param(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// define generic service trait
|
/// Generic service trait.
|
||||||
pub trait DataService: Send + Sync + 'static {
|
pub trait DataService: Send + Sync + 'static {
|
||||||
|
/// Get data from service.
|
||||||
fn get_data(&self) -> String;
|
fn get_data(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,7 +77,7 @@ impl DataService for MockDataService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// define generic server
|
/// Generic server.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GenericServer<DS: DataService> {
|
pub struct GenericServer<DS: DataService> {
|
||||||
data_service: Arc<DS>,
|
data_service: Arc<DS>,
|
||||||
|
|
@ -76,6 +86,7 @@ pub struct GenericServer<DS: DataService> {
|
||||||
|
|
||||||
#[tool_router]
|
#[tool_router]
|
||||||
impl<DS: DataService> GenericServer<DS> {
|
impl<DS: DataService> GenericServer<DS> {
|
||||||
|
/// Create data server instance.
|
||||||
pub fn new(data_service: DS) -> Self {
|
pub fn new(data_service: DS) -> Self {
|
||||||
Self {
|
Self {
|
||||||
data_service: Arc::new(data_service),
|
data_service: Arc::new(data_service),
|
||||||
|
|
@ -142,22 +153,26 @@ async fn test_tool_macros_with_optional_param() {
|
||||||
|
|
||||||
impl GetWeatherRequest {}
|
impl GetWeatherRequest {}
|
||||||
|
|
||||||
// Struct defined for testing optional field schema generation
|
/// Struct defined for testing optional field schema generation.
|
||||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||||
pub struct OptionalFieldTestSchema {
|
pub struct OptionalFieldTestSchema {
|
||||||
|
/// Field description.
|
||||||
#[schemars(description = "An optional description field")]
|
#[schemars(description = "An optional description field")]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Struct defined for testing optional i64 field schema generation and null handling
|
/// Struct defined for testing optional i64 field schema generation and null handling.
|
||||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||||
pub struct OptionalI64TestSchema {
|
pub struct OptionalI64TestSchema {
|
||||||
|
/// Optional count field.
|
||||||
#[schemars(description = "An optional i64 field")]
|
#[schemars(description = "An optional i64 field")]
|
||||||
pub count: Option<i64>,
|
pub count: Option<i64>,
|
||||||
pub mandatory_field: String, // Added to ensure non-empty object schema
|
|
||||||
|
/// Added to ensure non-empty object schema.
|
||||||
|
pub mandatory_field: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dummy struct to host the test tool method
|
/// Dummy struct to host the test tool method.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct OptionalSchemaTester {
|
pub struct OptionalSchemaTester {
|
||||||
tool_router: ToolRouter<Self>,
|
tool_router: ToolRouter<Self>,
|
||||||
|
|
@ -170,6 +185,7 @@ impl Default for OptionalSchemaTester {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OptionalSchemaTester {
|
impl OptionalSchemaTester {
|
||||||
|
/// Create instance of optional schema tester service.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
tool_router: Self::tool_router(),
|
tool_router: Self::tool_router(),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue