docs_mcp/tools/
crate_impls_list.rs1use rmcp::{ErrorData, model::{CallToolResult, Content}};
2use serde::Deserialize;
3use rmcp::schemars::{self, JsonSchema};
4use serde_json::json;
5
6use super::AppState;
7use crate::docsrs::{fetch_rustdoc_json, parser::type_to_string};
8
9#[derive(Debug, Deserialize, JsonSchema)]
10pub struct CrateImplsListParams {
11 pub name: String,
13 pub version: Option<String>,
15 pub trait_path: Option<String>,
17 pub type_path: Option<String>,
19 pub search: Option<String>,
21 pub limit: Option<usize>,
23}
24
25pub async fn execute(state: &AppState, params: CrateImplsListParams) -> Result<CallToolResult, ErrorData> {
26 if params.trait_path.is_none() && params.type_path.is_none() {
27 return Err(ErrorData::invalid_params(
28 "Either trait_path or type_path must be specified.\n\
29 - To find all types that implement a trait: trait_path=\"Default\"\n\
30 - To find all traits a specific type implements: type_path=\"MyStruct\"\n\
31 Use crate_item_list first to discover item names in the crate.",
32 None,
33 ));
34 }
35
36 let name = ¶ms.name;
37 let version = state.resolve_version(name, params.version.as_deref()).await
38 .map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
39
40 let doc = match fetch_rustdoc_json(name, &version, &state.client, &state.cache).await {
41 Ok(d) => d,
42 Err(crate::error::DocsError::DocsNotFound { .. }) => {
43 return Err(ErrorData::invalid_params(
44 format!("No docs.rs build found for {name} {version}. \
45 The latest version may not have been built yet. \
46 Try specifying an older version with the 'version' parameter."),
47 None,
48 ));
49 }
50 Err(e) => return Err(ErrorData::internal_error(e.to_string(), None)),
51 };
52
53 let search_lower = params.search.as_deref().map(|s| s.to_lowercase());
54 let limit = params.limit.unwrap_or(50).min(200);
55
56 if let Some(ref trait_path) = params.trait_path {
57 let trait_last = trait_path.rsplit("::").next().unwrap_or(trait_path.as_str());
60
61 let mut implementors: Vec<serde_json::Value> = vec![];
62 for item in doc.index.values() {
63 let Some(impl_inner) = item.inner_for("impl") else { continue };
64 if impl_inner.get("is_synthetic").and_then(|v| v.as_bool()).unwrap_or(false) {
66 continue;
67 }
68 let Some(trait_val) = impl_inner.get("trait") else { continue };
70 if trait_val.is_null() { continue; }
71
72 let t_name = trait_val.get("path").and_then(|v| v.as_str()).unwrap_or("");
74 let t_matches = t_name == trait_last
75 || t_name == trait_path.as_str()
76 || trait_path.ends_with(&format!("::{t_name}"));
77 if !t_matches { continue; }
78
79 let for_val = impl_inner.get("for");
81 let for_name: String = for_val
82 .and_then(|f| f.get("resolved_path"))
83 .and_then(|rp| rp.get("path").and_then(|v| v.as_str()))
84 .map(|s| s.to_string())
85 .unwrap_or_else(|| for_val.map(type_to_string).unwrap_or_default());
86
87 if for_name.is_empty() { continue; }
88
89 if let Some(ref search) = search_lower {
90 if !for_name.to_lowercase().contains(search.as_str()) {
91 continue;
92 }
93 }
94
95 if implementors.len() >= limit { break; }
96
97 let impl_generics: Vec<&str> = impl_inner
99 .get("generics").and_then(|g| g.get("params")).and_then(|p| p.as_array())
100 .map(|ps| ps.iter().filter_map(|p| p.get("name").and_then(|v| v.as_str())).collect())
101 .unwrap_or_default();
102
103 implementors.push(json!({
104 "type_name": for_name,
105 "impl_generics": if impl_generics.is_empty() { None } else { Some(impl_generics) },
106 }));
107 }
108
109 let output = json!({
110 "name": name,
111 "version": version,
112 "trait_path": trait_path,
113 "count": implementors.len(),
114 "implementors": implementors,
115 });
116 let json = serde_json::to_string_pretty(&output)
117 .map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
118 return Ok(CallToolResult::success(vec![Content::text(json)]));
119 }
120
121 let type_path_str = params.type_path.as_deref().unwrap();
124 let target_parts: Vec<&str> = type_path_str.split("::").collect();
125
126 let item_id = doc.paths.iter()
128 .find(|(_, p)| p.full_path() == type_path_str)
129 .or_else(|| {
130 doc.paths.iter().find(|(_, p)| {
131 let parts = &p.path;
132 if parts.is_empty() || target_parts.is_empty() { return false; }
133 if parts[0] != target_parts[0] { return false; }
134 let stored_rest = &parts[1..];
135 let target_rest = &target_parts[1..];
136 if target_rest.is_empty() { return false; }
137 let mut ti = 0;
138 for s in stored_rest {
139 if ti < target_rest.len() && *s == target_rest[ti] { ti += 1; }
140 }
141 ti == target_rest.len()
142 })
143 })
144 .map(|(id, _)| id.clone());
145
146 let item_id = item_id.ok_or_else(|| {
147 ErrorData::invalid_params(
148 format!("Type '{type_path_str}' not found in {name} {version}"),
149 None,
150 )
151 })?;
152
153 let item = doc.index.get(&item_id).ok_or_else(|| {
154 ErrorData::internal_error(format!("Item ID {item_id} not in index"), None)
155 })?;
156
157 let impl_ids: Vec<String> = {
159 let mut ids = vec![];
160 for kind in &["struct", "enum", "union", "primitive"] {
161 if let Some(inner) = item.inner_for(kind) {
162 if let Some(impls) = inner.get("impls").and_then(|v| v.as_array()) {
163 for v in impls {
164 if let Some(id) = match v {
165 serde_json::Value::Number(n) => Some(n.to_string()),
166 serde_json::Value::String(s) => Some(s.clone()),
167 _ => None,
168 } {
169 ids.push(id);
170 }
171 }
172 break;
173 }
174 }
175 }
176 ids
177 };
178
179 let mut implementations: Vec<serde_json::Value> = vec![];
180 for impl_id in &impl_ids {
181 let Some(impl_item) = doc.index.get(impl_id) else { continue };
182 let Some(impl_inner) = impl_item.inner_for("impl") else { continue };
183
184 let trait_val = impl_inner.get("trait");
185 let is_inherent = trait_val.map(|t| t.is_null()).unwrap_or(true);
186 let is_synthetic = impl_inner.get("is_synthetic").and_then(|v| v.as_bool()).unwrap_or(false);
189 if is_synthetic { continue; }
190
191 let trait_name: Option<String> = if is_inherent {
193 None
194 } else {
195 trait_val.map(type_to_string)
196 };
197
198 if let Some(ref search) = search_lower {
199 let name_str = trait_name.as_deref().unwrap_or("inherent");
200 if !name_str.to_lowercase().contains(search.as_str()) {
201 continue;
202 }
203 }
204
205 if implementations.len() >= limit { break; }
206
207 implementations.push(json!({
208 "trait_path": trait_name,
209 "is_inherent": is_inherent,
210 }));
211 }
212
213 let output = json!({
214 "name": name,
215 "version": version,
216 "type_path": type_path_str,
217 "count": implementations.len(),
218 "implementations": implementations,
219 });
220 let json = serde_json::to_string_pretty(&output)
221 .map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
222 Ok(CallToolResult::success(vec![Content::text(json)]))
223}