callisto-graph 0.3.1

Dependency graph, cascade, aggregation, and config resolution for callisto.
Documentation
use callisto_model::{CommandRunner, StatusReport, SCHEMA_VERSION};

use crate::changed::changed_since_last_tag;
use crate::commands::escalate;
use crate::error::GraphError;
use crate::resolver::DependencyResolver;
use crate::Workspace;

#[derive(Clone, Debug, Default)]
pub struct StatusOptions {
    pub strict: bool,
    pub strict_graph: bool,
}

pub fn status<R: CommandRunner, D: DependencyResolver>(
    ws: &Workspace<'_, R, D>,
    opts: &StatusOptions,
) -> Result<StatusReport, GraphError> {
    let mut packages = Vec::new();
    let base_versions = ws.base_versions()?;
    let loaded_changesets = crate::load_changesets(&ws.root, &ws.config)?;

    for pkg in ws.graph.packages() {
        let current_version = base_versions.get(&pkg.id).cloned().ok_or_else(|| {
            GraphError::Manifest(callisto_model::ManifestError::MissingField {
                path: pkg
                    .manifests
                    .first()
                    .map(|m| m.path.clone())
                    .unwrap_or_default(),
                field: "version",
            })
        })?;
        let last_tag = ws.tags.last_tag(&pkg.id).map(|t| t.name.clone());
        let changed = changed_since_last_tag(ws.runner, &ws.root, pkg, &ws.tags)?;

        let mut pkg_changesets = Vec::new();
        let mut max_sev: Option<callisto_model::Severity> = None;

        for lc in &loaded_changesets {
            for entry in &lc.changeset.entries {
                if entry.name == pkg.id.to_string() {
                    let name = lc
                        .path
                        .file_stem()
                        .and_then(|s| s.to_str())
                        .unwrap_or("unknown")
                        .to_string();
                    pkg_changesets.push(name);
                    max_sev = match (max_sev, entry.severity) {
                        (None, s) => Some(s),
                        (Some(cur), s) => Some(cur.max(s)),
                    };
                }
            }
        }

        packages.push(callisto_model::StatusPackageRecord {
            package: pkg.id.clone(),
            current_version,
            last_tag,
            pending_severity: max_sev,
            changed_since_last_tag: changed,
            pending_changesets: pkg_changesets,
        });
    }

    let mut diagnostics = ws.graph.diagnostics().to_vec();
    escalate(&mut diagnostics, opts.strict, opts.strict_graph);

    Ok(StatusReport {
        schema_version: SCHEMA_VERSION,
        packages,
        diagnostics,
    })
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_status_package_record_structure() {
        let rec = callisto_model::StatusPackageRecord {
            package: callisto_model::PackageId::parse("test-pkg").unwrap(),
            current_version: callisto_model::Version::semver(1, 0, 0),
            last_tag: None,
            pending_severity: Some(callisto_model::Severity::Minor),
            changed_since_last_tag: false,
            pending_changesets: vec!["my-changeset".to_string()],
        };
        assert_eq!(rec.pending_changesets.len(), 1);
        assert_eq!(rec.pending_severity, Some(callisto_model::Severity::Minor));
    }
}