#![allow(dead_code)]
use crate::model::ParamDef;
use crate::model::ToolDef;
use proc_macro2::TokenStream;
use quote::format_ident;
use quote::quote;
pub fn generate_param_struct(tool: &ToolDef, struct_name: &str, derives: &[&str]) -> TokenStream {
let struct_ident = format_ident!("{}", struct_name);
let params: Vec<&ParamDef> = tool.params.iter().filter(|p| p.name != "self").collect();
if params.is_empty() {
let derive_tokens = generate_derives(derives);
return quote! {
#derive_tokens
pub struct #struct_ident {}
};
}
let fields = params.iter().map(|param| {
let field_name = ¶m.name;
let field_type = ¶m.ty;
let docs = if let Some(desc) = ¶m.metadata.description {
quote! { #[doc = #desc] }
} else {
quote! {}
};
let serde_attrs = generate_serde_attrs(param);
quote! {
#docs
#serde_attrs
pub #field_name: #field_type
}
});
let derive_tokens = generate_derives(derives);
quote! {
#derive_tokens
pub struct #struct_ident {
#(#fields),*
}
}
}
pub fn generate_inline_param_struct(
tool: &ToolDef,
struct_name: &str,
derives: &[&str],
) -> TokenStream {
let struct_ident = format_ident!("{}", struct_name);
let params: Vec<&ParamDef> = tool.params.iter().filter(|p| p.name != "self").collect();
if params.is_empty() {
let derive_tokens = generate_derives(derives);
return quote! {
#derive_tokens
struct #struct_ident {}
};
}
let fields = params.iter().map(|param| {
let field_name = ¶m.name;
let field_type = ¶m.ty;
let serde_attrs = generate_serde_attrs(param);
quote! {
#serde_attrs
#field_name: #field_type
}
});
let derive_tokens = generate_derives(derives);
quote! {
#derive_tokens
struct #struct_ident {
#(#fields),*
}
}
}
fn generate_derives(derives: &[&str]) -> TokenStream {
let derive_idents: Vec<TokenStream> = derives
.iter()
.map(|d| {
match *d {
"Serialize" => quote!(serde::Serialize),
"Deserialize" => quote!(serde::Deserialize),
"JsonSchema" => quote!(schemars::JsonSchema),
"ToSchema" => quote!(utoipa::ToSchema),
"Debug" => quote!(Debug),
"Clone" => quote!(Clone),
_ => {
let ident = format_ident!("{}", d);
quote!(#ident)
}
}
})
.collect();
if derive_idents.is_empty() {
quote! {}
} else {
quote! {
#[derive(#(#derive_idents),*)]
}
}
}
fn generate_serde_attrs(param: &ParamDef) -> TokenStream {
let mut attrs = vec![];
if is_rust_keyword(¶m.name.to_string()) {
let renamed = format!("{}_", param.name);
attrs.push(quote! { #[serde(rename = #renamed)] });
}
if param.is_optional {
attrs.push(quote! { #[serde(default)] });
}
quote! { #(#attrs)* }
}
fn is_rust_keyword(name: &str) -> bool {
matches!(
name,
"as" | "break"
| "const"
| "continue"
| "crate"
| "else"
| "enum"
| "extern"
| "false"
| "fn"
| "for"
| "if"
| "impl"
| "in"
| "let"
| "loop"
| "match"
| "mod"
| "move"
| "mut"
| "pub"
| "ref"
| "return"
| "self"
| "Self"
| "static"
| "struct"
| "super"
| "trait"
| "true"
| "type"
| "unsafe"
| "use"
| "where"
| "while"
| "async"
| "await"
| "dyn"
| "abstract"
| "become"
| "box"
| "do"
| "final"
| "macro"
| "override"
| "priv"
| "typeof"
| "unsized"
| "virtual"
| "yield"
| "try"
)
}
pub fn generate_method_call_from_struct(tool: &ToolDef, struct_var: &str) -> TokenStream {
let method_name = &tool.method_name;
let params: Vec<&ParamDef> = tool.params.iter().filter(|p| p.name != "self").collect();
let args = params.iter().map(|param| {
let field_name = ¶m.name;
let struct_ident = format_ident!("{}", struct_var);
quote! { #struct_ident.#field_name }
});
if tool.is_async {
quote! {
self.#method_name(#(#args),*).await
}
} else {
quote! {
self.#method_name(#(#args),*)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::ParamMetadata;
use crate::model::ParamSource;
use syn::Type;
use syn::parse_quote;
fn create_test_param(name: &str, ty: Type, is_optional: bool) -> ParamDef {
ParamDef {
name: format_ident!("{}", name),
ty,
source: ParamSource::Body,
is_optional,
metadata: ParamMetadata {
description: Some(format!("Test parameter {name}")),
..Default::default()
},
}
}
#[test]
fn test_generate_param_struct() {
let tool = ToolDef {
method_name: format_ident!("test_method"),
tool_name: "test".to_string(),
params: vec![
create_test_param("name", parse_quote!(String), false),
create_test_param("count", parse_quote!(Option<u32>), true),
],
return_type: parse_quote!(String),
is_async: false,
visibility: parse_quote!(pub),
metadata: Default::default(),
};
let tokens = generate_param_struct(&tool, "TestParams", &["Deserialize", "JsonSchema"]);
let generated = tokens.to_string();
assert!(generated.contains("struct TestParams"));
assert!(generated.contains("pub name : String"));
assert!(generated.contains("pub count : Option < u32 >"));
assert!(generated.contains("serde :: Deserialize"));
assert!(generated.contains("schemars :: JsonSchema"));
}
#[test]
fn test_empty_param_struct() {
let tool = ToolDef {
method_name: format_ident!("test_method"),
tool_name: "test".to_string(),
params: vec![],
return_type: parse_quote!(()),
is_async: false,
visibility: parse_quote!(pub),
metadata: Default::default(),
};
let tokens = generate_param_struct(&tool, "EmptyParams", &["Deserialize"]);
let generated = tokens.to_string();
assert!(generated.contains("struct EmptyParams { }"));
}
}