rust_docs_mcp/deps/
mod.rs1pub mod outputs;
2pub mod tools;
3
4use rmcp::schemars;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Serialize, Deserialize, JsonSchema)]
10pub struct DependencyInfo {
11 pub crate_info: CrateIdentifier,
13
14 pub direct_dependencies: Vec<Dependency>,
16
17 pub dependency_tree: Option<serde_json::Value>,
19
20 pub total_dependencies: usize,
22}
23
24#[derive(Debug, Serialize, Deserialize, JsonSchema)]
26pub struct CrateIdentifier {
27 pub name: String,
28 pub version: String,
29}
30
31#[derive(Debug, Serialize, Deserialize, JsonSchema)]
33pub struct Dependency {
34 pub name: String,
36
37 pub version_req: String,
39
40 pub resolved_version: Option<String>,
42
43 pub kind: String,
45
46 pub optional: bool,
48
49 pub features: Vec<String>,
51
52 pub target: Option<String>,
54}
55
56pub fn process_cargo_metadata(
58 metadata: &serde_json::Value,
59 crate_name: &str,
60 crate_version: &str,
61 include_tree: bool,
62 filter: Option<&str>,
63) -> anyhow::Result<DependencyInfo> {
64 let packages = metadata["packages"]
66 .as_array()
67 .ok_or_else(|| anyhow::anyhow!("No packages found in metadata"))?;
68
69 let package = packages
70 .iter()
71 .find(|p| {
72 p["name"].as_str() == Some(crate_name) && p["version"].as_str() == Some(crate_version)
73 })
74 .ok_or_else(|| {
75 anyhow::anyhow!(
76 "Package {}-{} not found in metadata",
77 crate_name,
78 crate_version
79 )
80 })?;
81
82 let mut direct_dependencies = Vec::new();
84
85 if let Some(deps) = package["dependencies"].as_array() {
86 for dep in deps {
87 let name = dep["name"].as_str().unwrap_or_default();
88
89 if let Some(filter_str) = filter
91 && !name.to_lowercase().contains(&filter_str.to_lowercase())
92 {
93 continue;
94 }
95
96 let resolved_version = find_resolved_version(metadata, crate_name, crate_version, name);
98
99 direct_dependencies.push(Dependency {
100 name: name.to_string(),
101 version_req: dep["req"].as_str().unwrap_or_default().to_string(),
102 resolved_version,
103 kind: dep["kind"].as_str().unwrap_or("normal").to_string(),
104 optional: dep["optional"].as_bool().unwrap_or(false),
105 features: dep["features"]
106 .as_array()
107 .map(|arr| {
108 arr.iter()
109 .filter_map(|v| v.as_str().map(String::from))
110 .collect()
111 })
112 .unwrap_or_default(),
113 target: dep["target"].as_str().map(String::from),
114 });
115 }
116 }
117
118 let total_dependencies = if let Some(resolve) = metadata["resolve"].as_object() {
120 if let Some(nodes) = resolve["nodes"].as_array() {
121 nodes
123 .iter()
124 .find(|n| {
125 n["id"]
126 .as_str()
127 .map(|id| id.starts_with(&format!("{crate_name} {crate_version}")))
128 .unwrap_or(false)
129 })
130 .and_then(|n| n["dependencies"].as_array())
131 .map(|deps| deps.len())
132 .unwrap_or(0)
133 } else {
134 direct_dependencies.len()
135 }
136 } else {
137 direct_dependencies.len()
138 };
139
140 Ok(DependencyInfo {
141 crate_info: CrateIdentifier {
142 name: crate_name.to_string(),
143 version: crate_version.to_string(),
144 },
145 direct_dependencies,
146 dependency_tree: if include_tree {
147 Some(metadata["resolve"].clone())
148 } else {
149 None
150 },
151 total_dependencies,
152 })
153}
154
155fn find_resolved_version(
157 metadata: &serde_json::Value,
158 parent_name: &str,
159 parent_version: &str,
160 dep_name: &str,
161) -> Option<String> {
162 let resolve = metadata["resolve"].as_object()?;
163 let nodes = resolve["nodes"].as_array()?;
164
165 let parent_node = nodes.iter().find(|n| {
167 n["id"]
168 .as_str()
169 .map(|id| id.starts_with(&format!("{parent_name} {parent_version}")))
170 .unwrap_or(false)
171 })?;
172
173 let deps = parent_node["deps"].as_array()?;
175 for dep in deps {
176 if dep["name"].as_str() == Some(dep_name) {
177 if let Some(pkg) = dep["pkg"].as_str() {
179 let parts: Vec<&str> = pkg.split(' ').collect();
181 if parts.len() >= 2 {
182 return Some(parts[1].to_string());
183 }
184 }
185 }
186 }
187
188 None
189}