govctl 0.11.0

Project governance CLI for RFC, ADR, and Work Item management
use crate::config::Config;
use crate::diagnostic::{Diagnostic, DiagnosticCode};
use crate::model::{ProjectIndex, ReleasesFile, WorkItemStatus};
use std::collections::{HashMap, HashSet};

pub fn validate_releases(
    releases: &ReleasesFile,
    index: &ProjectIndex,
    config: &Config,
) -> Vec<Diagnostic> {
    let mut diagnostics = Vec::new();
    let mut seen_versions = HashSet::new();
    let known_work_ids: HashSet<&str> = index
        .work_items
        .iter()
        .map(|work| work.meta().id.as_str())
        .collect();
    let work_statuses: HashMap<&str, WorkItemStatus> = index
        .work_items
        .iter()
        .map(|work| (work.meta().id.as_str(), work.meta().status))
        .collect();
    let mut released_work = HashMap::new();
    let releases_display = config
        .display_path(&config.releases_path())
        .display()
        .to_string();

    for release in &releases.releases {
        if !seen_versions.insert(release.version.as_str()) {
            diagnostics.push(Diagnostic::new(
                DiagnosticCode::E0702ReleaseDuplicate,
                format!("Duplicate release version: {}", release.version),
                releases_display.clone(),
            ));
        }

        for work_id in &release.refs {
            if !known_work_ids.contains(work_id.as_str()) {
                diagnostics.push(Diagnostic::new(
                    DiagnosticCode::E0705ReleaseRefNotFound,
                    format!(
                        "Release '{}' references unknown work item: {}",
                        release.version, work_id
                    ),
                    releases_display.clone(),
                ));
                continue;
            }

            if work_statuses.get(work_id.as_str()) != Some(&WorkItemStatus::Done) {
                diagnostics.push(Diagnostic::new(
                    DiagnosticCode::E0706ReleaseWorkNotDone,
                    format!(
                        "Release '{}' references Work Item '{}' with status other than done",
                        release.version, work_id
                    ),
                    releases_display.clone(),
                ));
            }

            if let Some(first_version) = released_work.insert(work_id.as_str(), &release.version)
                && first_version != &release.version
            {
                diagnostics.push(Diagnostic::new(
                    DiagnosticCode::E0707ReleaseWorkDuplicate,
                    format!(
                        "Work Item '{}' is referenced by releases '{}' and '{}'",
                        work_id, first_version, release.version
                    ),
                    releases_display.clone(),
                ));
            }
        }
    }

    diagnostics
}