cargo-uv 0.7.0

A simple Cargo tool for updating the version in your project.
Documentation
use std::fmt::Display;

use semver::Version;
use tracing::{info, instrument, trace};

use crate::{
    CargoFile, ReadToml, current_span,
    manifest::error::{ItemType, VersionlocationError},
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VersionType {
    Package,
    SetByWorkspace,
    WorkspacePackage,
}

#[derive(Debug, Clone, Copy)]
pub enum VersionLocation {
    Package,
    WorkspacePackage,
}

impl Display for VersionLocation {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                VersionLocation::Package => "package.version",
                VersionLocation::WorkspacePackage => "workspace.package.version",
            }
        )
    }
}

impl VersionLocation {
    #[track_caller]
    #[instrument(skip_all, fields(version, path))]
    pub fn get_version(
        &self,
        cargo_toml: &CargoFile<ReadToml>,
    ) -> Result<Version, VersionlocationError> {
        use crate::manifest::error::VersionLocationErrorKind as ErrKind;
        let path = cargo_toml.path();

        let set_err = |kind: ErrKind, context: Option<&'static str>| kind.to_error(path, context);
        let _span = current_span!();
        let document = cargo_toml
            .contents()
            .expect("Can't call this function without the document read.");
        tracing::trace!("have document");
        let ret = match self {
            VersionLocation::Package => {
                let package = document
                    .get("package")
                    .ok_or(ErrKind::PackageNotFound.to_error(path, Some("First check")))?;

                let package_table = package.as_table().ok_or_else(|| {
                    set_err(
                        ErrKind::ItemInvalid(package.into()),
                        Some("Tried to get package table"),
                    )
                })?;

                match package_table.get("version").ok_or(set_err(
                    ErrKind::NotFound(*self),
                    Some("Package table located."),
                ))? {
                    toml_edit::Item::Value(value) => {
                        Version::parse(value.as_str().unwrap()).map_err(|e| set_err(e.into(), None))
                    }
                    toml_edit::Item::Table(table) => {
                        let workspace_table = table.get("workspace").ok_or(set_err(
                            ErrKind::ItemInvalid(ItemType::Table),
                            Some("expected version.workspace = <bool>"),
                        ))?;
                        let val = workspace_table.as_bool().ok_or(set_err(
                            ErrKind::ItemInvalid(ItemType::Value),
                            Some("Expected bool"),
                        ))?;

                        let msg = format!("in the manifest file: version.workspace = {val}");
                        Err(ErrKind::SetByWorkspace.to_error(path, Some(msg)))
                    }

                    item => Err(set_err(ErrKind::ItemInvalid(item.into()), None)),
                }
            }
            VersionLocation::WorkspacePackage => {
                let workspace =
                    document
                        .get("workspace")
                        .ok_or(ErrKind::WorkspaceNotFound.to_error(
                            path,
                            Some(format!(
                                "Workspace Package: {}",
                                path.as_os_str().to_str().unwrap()
                            )),
                        ))?;

                let workspace = workspace.as_table().ok_or_else(|| {
                    set_err(
                        ErrKind::ItemInvalid(workspace.into()),
                        Some("Tried to get package table"),
                    )
                })?;
                let package = workspace
                    .get("package")
                    .ok_or(ErrKind::PackageNotFound.to_error(path, Some("")))?;

                let package = package.as_table().ok_or_else(|| {
                    set_err(
                        ErrKind::ItemInvalid(package.into()),
                        Some("Tried to get package table"),
                    )
                })?;
                match package.get("version").ok_or(set_err(
                    ErrKind::NotFound(*self),
                    Some("Package table located."),
                ))? {
                    toml_edit::Item::Value(value) => Version::parse(value.as_str().unwrap())
                        .map_err(|e| set_err(e.into(), Some("Workspace Version"))),
                    item => Err(set_err(ErrKind::ItemInvalid(item.into()), None)),
                }
            }
        };
        let version = ret?;
        current_span!().record("path", path.as_os_str().to_str().unwrap_or_default());
        current_span!().record("version", &version.to_string());
        info!("Version found");

        Ok(version)
    }

    #[track_caller]
    #[instrument(skip_all, fields(version, path))]
    pub fn set_version<'a>(
        &self,
        cargo_toml: &mut CargoFile<ReadToml>,
        version: &Version,
    ) -> Result<(), VersionlocationError> {
        use crate::manifest::error::VersionLocationErrorKind as ErrKind;
        let path = cargo_toml.path().to_path_buf();

        let set_err = |kind: ErrKind, context: Option<&'static str>| kind.to_error(&path, context);

        let _span = current_span!();

        let document = cargo_toml
            .contents_mut()
            .expect("Can't call this function without the document read.");

        trace!("have document");

        let ret = match self {
            VersionLocation::Package => {
                let package = document
                    .get_mut("package")
                    .ok_or(set_err(ErrKind::PackageNotFound, None))?;
                let pack_kind = ItemType::from(&*package);

                let package_table = package.as_table_mut().ok_or_else(|| {
                    set_err(
                        ErrKind::ItemInvalid(pack_kind),
                        Some("Tried to get package table"),
                    )
                })?;

                match package_table.get_mut("version").ok_or(set_err(
                    ErrKind::NotFound(*self),
                    Some("Package table located."),
                ))? {
                    toml_edit::Item::Value(value) => Ok(*value = version.to_string().into()),
                    item => Err(set_err(
                        ErrKind::ItemInvalid(item.into()),
                        Some("Invalid itemtype for setting package version."),
                    )),
                }
            }
            VersionLocation::WorkspacePackage => {
                let workspace = document
                    .get_mut("workspace")
                    .ok_or(set_err(ErrKind::WorkspaceNotFound, None))?;

                let ws_kind = ItemType::from(&*workspace);
                let workspace = workspace.as_table_mut().ok_or_else(|| {
                    set_err(
                        ErrKind::ItemInvalid(ws_kind.into()),
                        Some("Tried to get package table"),
                    )
                })?;
                let package = workspace
                    .get_mut("package")
                    .ok_or(set_err(ErrKind::PackageNotFound, None))?;
                let pack_kind = ItemType::from(&*package);

                let package = package.as_table_mut().ok_or_else(|| {
                    set_err(
                        ErrKind::ItemInvalid(pack_kind.into()),
                        Some("Tried to get package table"),
                    )
                })?;
                match package.get_mut("version").ok_or(set_err(
                    ErrKind::NotFound(*self),
                    Some("Package table located."),
                ))? {
                    toml_edit::Item::Value(value) => Ok(*value = version.to_string().into()),
                    item => Err(set_err(
                        ErrKind::ItemInvalid(item.into()),
                        Some("Invalid itemtype for setting workspace version."),
                    )),
                }
            }
        };
        let _version = ret?;
        current_span!().record("path", (&path).as_os_str().to_str().unwrap_or_default());
        info!("Version set: {version}");
        Ok(())
    }
}