Skip to main content

kdo_resolver/
cargo.rs

1//! Parser for `Cargo.toml` manifests (Rust projects).
2
3use crate::ManifestParser;
4use kdo_core::{DepKind, Dependency, KdoError, Language, Project};
5use std::path::Path;
6use tracing::debug;
7
8/// Parses Rust `Cargo.toml` manifests.
9pub struct CargoParser;
10
11impl ManifestParser for CargoParser {
12    fn manifest_name(&self) -> &str {
13        "Cargo.toml"
14    }
15
16    fn can_parse(&self, manifest_path: &Path) -> bool {
17        manifest_path
18            .file_name()
19            .map(|f| f == "Cargo.toml")
20            .unwrap_or(false)
21    }
22
23    fn parse(
24        &self,
25        manifest_path: &Path,
26        workspace_root: &Path,
27    ) -> Result<(Project, Vec<Dependency>), KdoError> {
28        let content = std::fs::read_to_string(manifest_path)?;
29        let doc: toml::Value = toml::from_str(&content).map_err(|e| KdoError::ParseError {
30            path: manifest_path.to_path_buf(),
31            source: e.into(),
32        })?;
33
34        // Skip workspace-root Cargo.toml (has [workspace] but no [package])
35        if doc.get("workspace").is_some() && doc.get("package").is_none() {
36            return Err(KdoError::ManifestNotFound(manifest_path.to_path_buf()));
37        }
38
39        let package = doc.get("package").ok_or_else(|| KdoError::ParseError {
40            path: manifest_path.to_path_buf(),
41            source: anyhow::anyhow!("missing [package] table"),
42        })?;
43
44        let name = package
45            .get("name")
46            .and_then(|v| v.as_str())
47            .ok_or_else(|| KdoError::ParseError {
48                path: manifest_path.to_path_buf(),
49                source: anyhow::anyhow!("missing package.name"),
50            })?
51            .to_string();
52
53        let description = package
54            .get("description")
55            .and_then(|v| v.as_str())
56            .map(String::from);
57
58        let project_dir = manifest_path
59            .parent()
60            .unwrap_or(Path::new("."))
61            .to_path_buf();
62
63        debug!(name = %name, path = %project_dir.display(), "parsed Cargo.toml");
64
65        let mut deps = Vec::new();
66
67        // Parse each dependency section
68        for (section, kind) in [
69            ("dependencies", DepKind::Source),
70            ("dev-dependencies", DepKind::Dev),
71            ("build-dependencies", DepKind::Build),
72        ] {
73            if let Some(table) = doc.get(section).and_then(|v| v.as_table()) {
74                for (dep_name, dep_val) in table {
75                    let dep = parse_cargo_dep(dep_name, dep_val, &kind, workspace_root);
76                    deps.push(dep);
77                }
78            }
79        }
80
81        let project = Project {
82            name,
83            path: project_dir,
84            language: Language::Rust,
85            manifest_path: manifest_path.to_path_buf(),
86            context_summary: description,
87            public_api_files: Vec::new(),
88            internal_files: Vec::new(),
89            content_hash: [0u8; 32],
90        };
91
92        Ok((project, deps))
93    }
94}
95
96fn parse_cargo_dep(
97    name: &str,
98    value: &toml::Value,
99    kind: &DepKind,
100    workspace_root: &Path,
101) -> Dependency {
102    let mut version_req = String::new();
103    let mut is_workspace = false;
104    let mut resolved_path = None;
105
106    match value {
107        toml::Value::String(v) => {
108            version_req = v.clone();
109        }
110        toml::Value::Table(t) => {
111            if let Some(v) = t.get("version").and_then(|v| v.as_str()) {
112                version_req = v.to_string();
113            }
114            if let Some(true) = t.get("workspace").and_then(|v| v.as_bool()) {
115                is_workspace = true;
116                version_req = "workspace".to_string();
117            }
118            if let Some(p) = t.get("path").and_then(|v| v.as_str()) {
119                resolved_path = Some(workspace_root.join(p));
120            }
121        }
122        _ => {}
123    }
124
125    Dependency {
126        name: name.to_string(),
127        version_req,
128        kind: kind.clone(),
129        is_workspace,
130        resolved_path,
131    }
132}