crates_docs/cli/
test_cmd.rs1use rust_mcp_sdk::schema::ContentBlock;
4use std::path::Path;
5use std::sync::Arc;
6
7#[allow(clippy::too_many_arguments)]
9pub async fn run_test_command(
10 config_path: &Path,
11 tool: &str,
12 crate_name: Option<&str>,
13 item_path: Option<&str>,
14 query: Option<&str>,
15 sort: Option<&str>,
16 version: Option<&str>,
17 limit: u32,
18 format: &str,
19) -> Result<(), Box<dyn std::error::Error>> {
20 tracing::info!("Testing tool: {}", tool);
21
22 let app_config = if config_path.exists() {
25 crate::config::AppConfig::from_file(config_path)
26 .map_err(|e| format!("Failed to load config file: {e}"))?
27 } else {
28 crate::config::AppConfig::default()
29 };
30
31 let _ = crate::utils::init_global_http_client(&app_config.performance);
35
36 let cache = crate::cache::create_cache(&app_config.cache)?;
37 let cache_arc: Arc<dyn crate::cache::Cache> = Arc::from(cache);
38
39 let doc_service = Arc::new(crate::tools::docs::DocService::with_config(
41 cache_arc,
42 &app_config.cache,
43 )?);
44
45 let registry = crate::tools::create_default_registry(&doc_service);
47
48 match tool {
49 "lookup_crate" => {
50 execute_lookup_crate(crate_name, version, format, ®istry).await?;
51 }
52 "search_crates" => {
53 execute_search_crates(query, sort, limit, format, ®istry).await?;
54 }
55 "lookup_item" => {
56 execute_lookup_item(crate_name, item_path, version, format, ®istry).await?;
57 }
58 "health_check" => {
59 execute_health_check(®istry).await?;
60 }
61 _ => {
62 return Err(format!("Unknown tool: {tool}").into());
63 }
64 }
65
66 println!("Tool test completed");
67 Ok(())
68}
69
70async fn execute_lookup_crate(
72 crate_name: Option<&str>,
73 version: Option<&str>,
74 format: &str,
75 registry: &crate::tools::ToolRegistry,
76) -> Result<(), Box<dyn std::error::Error>> {
77 if let Some(name) = crate_name {
78 println!("Testing crate lookup: {name} (version: {version:?})");
79 println!("Output format: {format}");
80
81 let mut arguments = serde_json::json!({
83 "crate_name": name,
84 "format": format
85 });
86
87 if let Some(v) = version {
88 arguments["version"] = serde_json::Value::String(v.to_string());
89 }
90
91 match registry.execute_tool("lookup_crate", arguments).await {
93 Ok(result) => print_tool_result(&result),
94 Err(e) => return Err(format!("Tool execution failed: {e}").into()),
95 }
96 } else {
97 return Err("lookup_crate requires --crate-name parameter".into());
98 }
99 Ok(())
100}
101
102async fn execute_search_crates(
104 query: Option<&str>,
105 sort: Option<&str>,
106 limit: u32,
107 format: &str,
108 registry: &crate::tools::ToolRegistry,
109) -> Result<(), Box<dyn std::error::Error>> {
110 if let Some(q) = query {
111 println!("Testing crate search: {q} (limit: {limit})");
112 println!("Sort order: {}", sort.unwrap_or("relevance"));
113 println!("Output format: {format}");
114
115 let mut arguments = serde_json::json!({
117 "query": q,
118 "limit": limit,
119 "format": format
120 });
121
122 if let Some(sort) = sort {
123 arguments["sort"] = serde_json::Value::String(sort.to_string());
124 }
125
126 match registry.execute_tool("search_crates", arguments).await {
128 Ok(result) => print_tool_result(&result),
129 Err(e) => return Err(format!("Tool execution failed: {e}").into()),
130 }
131 } else {
132 return Err("search_crates requires --query parameter".into());
133 }
134 Ok(())
135}
136
137fn display_item_path(crate_name: &str, item_path: &str) -> String {
145 let krate = crate_name.replace('-', "_");
146 let first = item_path.split("::").map(str::trim).find(|s| !s.is_empty());
147 if first.map(|s| s.replace('-', "_")) == Some(krate) {
148 item_path.trim().to_string()
149 } else {
150 format!("{crate_name}::{item_path}")
151 }
152}
153
154async fn execute_lookup_item(
156 crate_name: Option<&str>,
157 item_path: Option<&str>,
158 version: Option<&str>,
159 format: &str,
160 registry: &crate::tools::ToolRegistry,
161) -> Result<(), Box<dyn std::error::Error>> {
162 if let (Some(name), Some(path)) = (crate_name, item_path) {
163 println!(
164 "Testing item lookup: {} (version: {version:?})",
165 display_item_path(name, path)
166 );
167 println!("Output format: {format}");
168
169 let mut arguments = serde_json::json!({
171 "crate_name": name,
172 "item_path": path,
173 "format": format
174 });
175
176 if let Some(v) = version {
177 arguments["version"] = serde_json::Value::String(v.to_string());
178 }
179
180 match registry.execute_tool("lookup_item", arguments).await {
182 Ok(result) => print_tool_result(&result),
183 Err(e) => return Err(format!("Tool execution failed: {e}").into()),
184 }
185 } else {
186 return Err("lookup_item requires --crate-name and --item-path parameters".into());
187 }
188 Ok(())
189}
190
191async fn execute_health_check(
193 registry: &crate::tools::ToolRegistry,
194) -> Result<(), Box<dyn std::error::Error>> {
195 println!("Testing health check");
196
197 let arguments = serde_json::json!({
199 "check_type": "all",
200 "verbose": true
201 });
202
203 match registry.execute_tool("health_check", arguments).await {
205 Ok(result) => print_tool_result(&result),
206 Err(e) => return Err(format!("Tool execution failed: {e}").into()),
207 }
208 Ok(())
209}
210
211fn print_tool_result(result: &rust_mcp_sdk::schema::CallToolResult) {
213 println!("Tool executed successfully:");
214 if let Some(content) = result.content.first() {
215 match content {
216 ContentBlock::TextContent(text_content) => {
217 println!("{}", text_content.text);
218 }
219 other => {
220 println!("Non-text content: {other:?}");
221 }
222 }
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::display_item_path;
229
230 #[test]
231 fn drops_redundant_leading_crate_segment() {
232 assert_eq!(
234 display_item_path("std", "std::string::String"),
235 "std::string::String"
236 );
237 assert_eq!(
238 display_item_path("std", "std::collections"),
239 "std::collections"
240 );
241 }
242
243 #[test]
244 fn prepends_crate_when_prefix_absent() {
245 assert_eq!(
246 display_item_path("std", "string::String"),
247 "std::string::String"
248 );
249 assert_eq!(display_item_path("std", "collections"), "std::collections");
250 }
251
252 #[test]
253 fn normalizes_crate_name_hyphens() {
254 assert_eq!(
256 display_item_path("tokio-util", "tokio_util::codec::Framed"),
257 "tokio_util::codec::Framed"
258 );
259 assert_eq!(
261 display_item_path("tokio-util", "codec::Framed"),
262 "tokio-util::codec::Framed"
263 );
264 }
265}