1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use std::{cmp::Ordering, error::Error, fmt};
7
8use semver::{BuildMetadata, Prerelease};
9use serde::{Deserialize, Serialize};
10
11#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
13pub struct Version(pub semver::Version);
14
15impl Version {
16 #[must_use]
18 pub fn as_semver(&self) -> &semver::Version {
19 &self.0
20 }
21}
22
23impl fmt::Display for Version {
24 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
25 self.0.fmt(formatter)
26 }
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
31pub enum VersionBump {
32 Patch,
33 Minor,
34 Major,
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
39pub enum VersionPolicy {
40 StrictSemver,
41 RustUseDefault,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
46pub enum ReleaseLevel {
47 Patch,
48 Minor,
49 Major,
50 PreRelease,
51}
52
53#[derive(Debug)]
55pub struct VersionError(semver::Error);
56
57impl fmt::Display for VersionError {
58 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59 write!(formatter, "failed to parse semantic version: {}", self.0)
60 }
61}
62
63impl Error for VersionError {
64 fn source(&self) -> Option<&(dyn Error + 'static)> {
65 Some(&self.0)
66 }
67}
68
69impl From<semver::Error> for VersionError {
70 fn from(error: semver::Error) -> Self {
71 Self(error)
72 }
73}
74
75pub fn parse_version(value: &str) -> Result<Version, VersionError> {
77 Ok(Version(semver::Version::parse(value)?))
78}
79
80#[must_use]
82pub fn is_prerelease(version: &Version) -> bool {
83 !version.0.pre.is_empty()
84}
85
86#[must_use]
88pub fn next_patch(version: &Version) -> Version {
89 let mut next = version.0.clone();
90 next.patch += 1;
91 next.pre = Prerelease::EMPTY;
92 next.build = BuildMetadata::EMPTY;
93 Version(next)
94}
95
96#[must_use]
98pub fn next_minor(version: &Version) -> Version {
99 let mut next = version.0.clone();
100 next.minor += 1;
101 next.patch = 0;
102 next.pre = Prerelease::EMPTY;
103 next.build = BuildMetadata::EMPTY;
104 Version(next)
105}
106
107#[must_use]
109pub fn next_major(version: &Version) -> Version {
110 let mut next = version.0.clone();
111 next.major += 1;
112 next.minor = 0;
113 next.patch = 0;
114 next.pre = Prerelease::EMPTY;
115 next.build = BuildMetadata::EMPTY;
116 Version(next)
117}
118
119#[must_use]
121pub fn compare_versions(left: &Version, right: &Version) -> Ordering {
122 left.cmp(right)
123}
124
125#[cfg(test)]
126mod tests {
127 use std::cmp::Ordering;
128
129 use super::{
130 compare_versions, is_prerelease, next_major, next_minor, next_patch, parse_version,
131 };
132
133 #[test]
134 fn parses_versions() {
135 let version = parse_version("1.2.3").expect("version should parse");
136
137 assert_eq!(version.to_string(), "1.2.3");
138 assert_eq!(version.as_semver().major, 1);
139 assert!(parse_version("not-a-version").is_err());
140 }
141
142 #[test]
143 fn detects_prereleases() {
144 let stable = parse_version("1.2.3").expect("stable version should parse");
145 let prerelease = parse_version("1.2.3-alpha.1").expect("prerelease should parse");
146
147 assert!(!is_prerelease(&stable));
148 assert!(is_prerelease(&prerelease));
149 }
150
151 #[test]
152 fn bumps_versions() {
153 let version = parse_version("0.1.2-alpha.1+build.7").expect("version should parse");
154
155 assert_eq!(next_patch(&version).to_string(), "0.1.3");
156 assert_eq!(next_minor(&version).to_string(), "0.2.0");
157 assert_eq!(next_major(&version).to_string(), "1.0.0");
158 }
159
160 #[test]
161 fn compares_versions() {
162 let earlier = parse_version("0.1.0").expect("version should parse");
163 let later = parse_version("0.2.0").expect("version should parse");
164
165 assert_eq!(compare_versions(&earlier, &later), Ordering::Less);
166 assert_eq!(compare_versions(&later, &earlier), Ordering::Greater);
167 assert_eq!(compare_versions(&earlier, &earlier), Ordering::Equal);
168 }
169}