cargo-ratchet 0.1.0

Prevent resolved versions in Cargo.lock from regressing below a committed, self-advancing baseline
Documentation
use std::fmt;

use cargo_lock::Version;
use serde::Serialize;

/// A place where resolved `Cargo.lock` state regressed below the baseline.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Violation {
    /// The highest version of a package name regressed below the baseline's
    /// highest version for that name.
    PackageVersionRegressed {
        name: String,
        baseline_version: Version,
        current_version: Version,
        baseline_source: Option<String>,
        current_source: Option<String>,
    },
    /// A resolved crates.io dependency edge regressed: the highest version of
    /// `dependency_name` that `package_name` resolves to dropped below the
    /// baseline.
    DependencyEdgeRegressed {
        package_name: String,
        baseline_package_version: Version,
        current_package_version: Version,
        dependency_name: String,
        baseline_dependency_version: Version,
        current_dependency_version: Version,
    },
}

impl fmt::Display for Violation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Violation::PackageVersionRegressed {
                name,
                baseline_version,
                current_version,
                baseline_source,
                current_source,
            } => write!(
                f,
                "package version regressed: {name} {baseline_version}{} became {name} {current_version}{}",
                source_suffix(baseline_source),
                source_suffix(current_source),
            ),
            Violation::DependencyEdgeRegressed {
                package_name,
                baseline_package_version,
                current_package_version,
                dependency_name,
                baseline_dependency_version,
                current_dependency_version,
            } => write!(
                f,
                "resolved dependency edge regressed: {package_name} {baseline_package_version} -> {dependency_name} {baseline_dependency_version} became {package_name} {current_package_version} -> {dependency_name} {current_dependency_version}",
            ),
        }
    }
}

fn source_suffix(source: &Option<String>) -> String {
    match source {
        Some(source) => format!(" ({source})"),
        None => String::new(),
    }
}