#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use std::{cmp::Ordering, error::Error, fmt};
use semver::{BuildMetadata, Prerelease};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Version(pub semver::Version);
impl Version {
#[must_use]
pub fn as_semver(&self) -> &semver::Version {
&self.0
}
}
impl fmt::Display for Version {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(formatter)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum VersionBump {
Patch,
Minor,
Major,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum VersionPolicy {
StrictSemver,
RustUseDefault,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ReleaseLevel {
Patch,
Minor,
Major,
PreRelease,
}
#[derive(Debug)]
pub struct VersionError(semver::Error);
impl fmt::Display for VersionError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "failed to parse semantic version: {}", self.0)
}
}
impl Error for VersionError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.0)
}
}
impl From<semver::Error> for VersionError {
fn from(error: semver::Error) -> Self {
Self(error)
}
}
pub fn parse_version(value: &str) -> Result<Version, VersionError> {
Ok(Version(semver::Version::parse(value)?))
}
#[must_use]
pub fn is_prerelease(version: &Version) -> bool {
!version.0.pre.is_empty()
}
#[must_use]
pub fn next_patch(version: &Version) -> Version {
let mut next = version.0.clone();
next.patch += 1;
next.pre = Prerelease::EMPTY;
next.build = BuildMetadata::EMPTY;
Version(next)
}
#[must_use]
pub fn next_minor(version: &Version) -> Version {
let mut next = version.0.clone();
next.minor += 1;
next.patch = 0;
next.pre = Prerelease::EMPTY;
next.build = BuildMetadata::EMPTY;
Version(next)
}
#[must_use]
pub fn next_major(version: &Version) -> Version {
let mut next = version.0.clone();
next.major += 1;
next.minor = 0;
next.patch = 0;
next.pre = Prerelease::EMPTY;
next.build = BuildMetadata::EMPTY;
Version(next)
}
#[must_use]
pub fn compare_versions(left: &Version, right: &Version) -> Ordering {
left.cmp(right)
}
#[cfg(test)]
mod tests {
use std::cmp::Ordering;
use super::{
compare_versions, is_prerelease, next_major, next_minor, next_patch, parse_version,
};
#[test]
fn parses_versions() {
let version = parse_version("1.2.3").expect("version should parse");
assert_eq!(version.to_string(), "1.2.3");
assert_eq!(version.as_semver().major, 1);
assert!(parse_version("not-a-version").is_err());
}
#[test]
fn detects_prereleases() {
let stable = parse_version("1.2.3").expect("stable version should parse");
let prerelease = parse_version("1.2.3-alpha.1").expect("prerelease should parse");
assert!(!is_prerelease(&stable));
assert!(is_prerelease(&prerelease));
}
#[test]
fn bumps_versions() {
let version = parse_version("0.1.2-alpha.1+build.7").expect("version should parse");
assert_eq!(next_patch(&version).to_string(), "0.1.3");
assert_eq!(next_minor(&version).to_string(), "0.2.0");
assert_eq!(next_major(&version).to_string(), "1.0.0");
}
#[test]
fn compares_versions() {
let earlier = parse_version("0.1.0").expect("version should parse");
let later = parse_version("0.2.0").expect("version should parse");
assert_eq!(compare_versions(&earlier, &later), Ordering::Less);
assert_eq!(compare_versions(&later, &earlier), Ordering::Greater);
assert_eq!(compare_versions(&earlier, &earlier), Ordering::Equal);
}
}