cargo-governor 2.0.0

Machine-First, LLM-Ready, CI/CD-Native release automation tool for Rust crates
Documentation
//! Cargo metadata parsing for plan service

use crate::error::Result;
use cargo_metadata::{Metadata, MetadataCommand};
use std::path::PathBuf;

/// Package information from cargo metadata
#[derive(Debug, Clone)]
pub struct PackageInfo {
    pub name: String,
    pub dependencies: Vec<String>,
}

/// Parse cargo metadata and extract workspace packages.
///
/// # Errors
///
/// Returns an error if:
/// - Cargo metadata cannot be read
/// - Current directory is invalid
#[allow(dead_code)]
pub(super) fn parse_cargo_metadata(workspace_path: &str) -> Result<(Metadata, Vec<PackageInfo>)> {
    let mut cmd = MetadataCommand::new();
    cmd.current_dir(PathBuf::from(workspace_path));
    cmd.no_deps();

    let metadata = cmd
        .exec()
        .map_err(|e| crate::error::Error::Config(format!("Failed to read cargo metadata: {e}")))?;

    let workspace_members: Vec<_> = metadata
        .workspace_members
        .iter()
        .map(std::string::ToString::to_string)
        .collect();

    let mut packages = Vec::new();

    for pkg in &metadata.packages {
        if workspace_members.contains(&pkg.id.to_string()) {
            let deps: Vec<String> = pkg
                .dependencies
                .iter()
                .filter(|d| {
                    d.path
                        .as_ref()
                        .is_some_and(|p| p.starts_with(&metadata.workspace_root))
                })
                .map(|d| d.name.clone())
                .collect();

            packages.push(PackageInfo {
                name: pkg.name.clone(),
                dependencies: deps,
            });
        }
    }

    Ok((metadata, packages))
}

/// Get current version from Cargo.toml
///
/// # Errors
///
/// Returns an error if:
/// - Cargo.toml cannot be read
/// - TOML parsing fails
/// - No version found
#[allow(dead_code)]
pub(super) fn get_current_version(workspace_path: &str) -> Result<String> {
    let cargo_toml_path = PathBuf::from(workspace_path).join("Cargo.toml");
    let content = std::fs::read_to_string(&cargo_toml_path)
        .map_err(|e| crate::error::Error::Io(format!("Failed to read Cargo.toml: {e}")))?;

    let value: toml::Value = toml::from_str(&content)
        .map_err(|e| crate::error::Error::Config(format!("Failed to parse Cargo.toml: {e}")))?;

    let package = value
        .get("workspace")
        .and_then(|w| w.get("package"))
        .or_else(|| value.get("package"));

    package
        .and_then(|p| p.get("version"))
        .and_then(|v| v.as_str())
        .map(String::from)
        .ok_or_else(|| crate::error::Error::Config("No version found in Cargo.toml".to_string()))
}