cargo-uv 0.7.0

A simple Cargo tool for updating the version in your project.
Documentation
use std::{
    fmt::{self, Display, Formatter},
    panic::Location,
    path::PathBuf,
};

use miette::Diagnostic;
use rusty_viking::EnumDisplay;

use crate::VersionLocation;

#[derive(Debug, thiserror::Error, Diagnostic)]
pub struct CargoFileError {
    #[source]
    kind: CargoFileErrorKind,
    path: PathBuf,
    #[help("Call location: {location_called}")]
    location_called: Location<'static>,
}

impl CargoFileError {
    pub fn kind(&self) -> &CargoFileErrorKind {
        &self.kind
    }
}

impl Display for CargoFileError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "Cargo.toml: {}", self.path.display())
    }
}

// #[allow(dead_code)]
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum CargoFileErrorKind {
    #[error("No root package")]
    NoRootPackage,
    #[error("No workspace package version")]
    NoWorkspaceVersion,
    #[error("No package version")]
    NoPackageVersion,
    #[error("No workspace or root package version is present")]
    NoPackageOrWorkspaceVersion,
    #[error("This package version is set by the workspace")]
    SetByWorkspace,
    #[error("Cargo file error with Version Location")]
    LocationError(#[from] VersionlocationError),
}

impl CargoFileErrorKind {
    #[track_caller]
    pub fn to_error(self, path: impl Into<PathBuf>) -> CargoFileError {
        CargoFileError {
            kind: self,
            path: path.into(),
            location_called: *Location::caller(),
        }
    }
}

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum VersionLocationErrorKind {
    #[error("Version is set by workspace.")]
    SetByWorkspace,
    #[error("Version not located in {0}")]
    NotFound(VersionLocation),
    #[error("No package defined in toml file.")]
    PackageNotFound,
    #[error("No workspace defined in toml file.")]
    WorkspaceNotFound,
    #[error("Invalid item type of {0}")]
    ItemInvalid(ItemType),
    #[error("Invalid semver: {0}")]
    SemverError(semver::Error),
}

impl From<semver::Error> for VersionLocationErrorKind {
    fn from(value: semver::Error) -> Self {
        Self::SemverError(value)
    }
}

impl VersionLocationErrorKind {
    #[track_caller]
    pub fn to_error(
        self,
        path: impl Into<PathBuf>,
        context: Option<impl ToString + std::default::Default>,
    ) -> VersionlocationError {
        VersionlocationError {
            kind: self,
            path: path.into(),
            location_called: Location::caller(),
            context: context.unwrap_or_default().to_string(),
        }
    }
}

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
#[error("{context}")]
pub struct VersionlocationError {
    #[source]
    kind: VersionLocationErrorKind,
    path: PathBuf,
    #[help("Call location: {location_called}")]
    location_called: &'static Location<'static>,
    context: String,
}

impl VersionlocationError {
    pub fn new(
        kind: VersionLocationErrorKind,
        path: PathBuf,
        location_called: &'static Location<'static>,
        context: String,
    ) -> Self {
        Self {
            kind,
            path,
            location_called,
            context,
        }
    }

    pub fn path(&self) -> &PathBuf {
        &self.path
    }

    pub fn kind(&self) -> &VersionLocationErrorKind {
        &self.kind
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumDisplay)]
#[Title]
pub enum ItemType {
    None,
    Value,
    Table,
    ArrayOfTables,
}

impl From<toml_edit::Item> for ItemType {
    fn from(item: toml_edit::Item) -> Self {
        match item {
            toml_edit::Item::None => ItemType::None,
            toml_edit::Item::Value(_) => ItemType::Value,
            toml_edit::Item::Table(_) => ItemType::Table,
            toml_edit::Item::ArrayOfTables(_) => ItemType::ArrayOfTables,
        }
    }
}
impl From<&toml_edit::Item> for ItemType {
    fn from(item: &toml_edit::Item) -> Self {
        match item {
            toml_edit::Item::None => ItemType::None,
            toml_edit::Item::Value(_) => ItemType::Value,
            toml_edit::Item::Table(_) => ItemType::Table,
            toml_edit::Item::ArrayOfTables(_) => ItemType::ArrayOfTables,
        }
    }
}
impl From<&mut toml_edit::Item> for ItemType {
    fn from(item: &mut toml_edit::Item) -> Self {
        match item {
            toml_edit::Item::None => ItemType::None,
            toml_edit::Item::Value(_) => ItemType::Value,
            toml_edit::Item::Table(_) => ItemType::Table,
            toml_edit::Item::ArrayOfTables(_) => ItemType::ArrayOfTables,
        }
    }
}