1use std::fmt::{self, Debug, Display};
6use std::str::FromStr;
7
8use thiserror::Error;
9
10#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
12pub struct SemanticVersion {
13 major: u32,
14 minor: u32,
15 patch: u32,
16}
17
18#[cfg(feature = "serde")]
19impl serde::Serialize for SemanticVersion {
20 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
21 where
22 S: serde::Serializer,
23 {
24 serializer.serialize_str(&format!("{self}"))
25 }
26}
27
28#[cfg(feature = "serde")]
29impl<'de> serde::Deserialize<'de> for SemanticVersion {
30 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
31 where
32 D: serde::Deserializer<'de>,
33 {
34 let s = String::deserialize(deserializer)?;
35 FromStr::from_str(&s).map_err(serde::de::Error::custom)
36 }
37}
38
39impl SemanticVersion {
41 pub fn new(major: u32, minor: u32, patch: u32) -> Self {
44 Self {
45 major,
46 minor,
47 patch,
48 }
49 }
50
51 pub fn zero() -> Self {
53 Self::new(0, 0, 0)
54 }
55
56 pub fn one() -> Self {
58 Self::new(1, 0, 0)
59 }
60
61 pub fn two() -> Self {
63 Self::new(2, 0, 0)
64 }
65}
66
67impl From<(u32, u32, u32)> for SemanticVersion {
69 fn from(tuple: (u32, u32, u32)) -> Self {
70 let (major, minor, patch) = tuple;
71 Self::new(major, minor, patch)
72 }
73}
74
75impl From<&(u32, u32, u32)> for SemanticVersion {
77 fn from(tuple: &(u32, u32, u32)) -> Self {
78 let (major, minor, patch) = *tuple;
79 Self::new(major, minor, patch)
80 }
81}
82
83impl From<&SemanticVersion> for SemanticVersion {
85 fn from(v: &SemanticVersion) -> Self {
86 *v
87 }
88}
89
90impl From<SemanticVersion> for (u32, u32, u32) {
92 fn from(v: SemanticVersion) -> Self {
93 (v.major, v.minor, v.patch)
94 }
95}
96
97impl SemanticVersion {
99 pub fn bump_patch(self) -> Self {
101 Self::new(self.major, self.minor, self.patch + 1)
102 }
103
104 pub fn bump_minor(self) -> Self {
106 Self::new(self.major, self.minor + 1, 0)
107 }
108
109 pub fn bump_major(self) -> Self {
111 Self::new(self.major + 1, 0, 0)
112 }
113}
114
115#[derive(Error, Debug, PartialEq, Eq)]
117pub enum VersionParseError {
118 #[error("version {full_version} must contain 3 numbers separated by dot")]
120 NotThreeParts {
121 full_version: String,
123 },
124 #[error("cannot parse '{version_part}' in '{full_version}' as u32: {parse_error}")]
126 ParseIntError {
127 full_version: String,
129 version_part: String,
131 parse_error: String,
133 },
134}
135
136impl FromStr for SemanticVersion {
137 type Err = VersionParseError;
138
139 fn from_str(s: &str) -> Result<Self, Self::Err> {
140 let parse_u32 = |part: &str| {
141 part.parse::<u32>().map_err(|e| Self::Err::ParseIntError {
142 full_version: s.to_string(),
143 version_part: part.to_string(),
144 parse_error: e.to_string(),
145 })
146 };
147
148 let mut parts = s.split('.');
149 match (parts.next(), parts.next(), parts.next(), parts.next()) {
150 (Some(major), Some(minor), Some(patch), None) => {
151 let major = parse_u32(major)?;
152 let minor = parse_u32(minor)?;
153 let patch = parse_u32(patch)?;
154 Ok(Self {
155 major,
156 minor,
157 patch,
158 })
159 }
160 _ => Err(Self::Err::NotThreeParts {
161 full_version: s.to_string(),
162 }),
163 }
164 }
165}
166
167#[test]
168fn from_str_for_semantic_version() {
169 let parse = |str: &str| str.parse::<SemanticVersion>();
170 assert!(
171 parse(
172 &SemanticVersion {
173 major: 0,
174 minor: 1,
175 patch: 0
176 }
177 .to_string()
178 )
179 .is_ok()
180 );
181 assert!(parse("1.2.3").is_ok());
182 assert_eq!(
183 parse("1.abc.3"),
184 Err(VersionParseError::ParseIntError {
185 full_version: "1.abc.3".to_owned(),
186 version_part: "abc".to_owned(),
187 parse_error: "invalid digit found in string".to_owned(),
188 })
189 );
190 assert_eq!(
191 parse("1.2.-3"),
192 Err(VersionParseError::ParseIntError {
193 full_version: "1.2.-3".to_owned(),
194 version_part: "-3".to_owned(),
195 parse_error: "invalid digit found in string".to_owned(),
196 })
197 );
198 assert_eq!(
199 parse("1.2.9876543210"),
200 Err(VersionParseError::ParseIntError {
201 full_version: "1.2.9876543210".to_owned(),
202 version_part: "9876543210".to_owned(),
203 parse_error: "number too large to fit in target type".to_owned(),
204 })
205 );
206 assert_eq!(
207 parse("1.2"),
208 Err(VersionParseError::NotThreeParts {
209 full_version: "1.2".to_owned(),
210 })
211 );
212 assert_eq!(
213 parse("1.2.3."),
214 Err(VersionParseError::NotThreeParts {
215 full_version: "1.2.3.".to_owned(),
216 })
217 );
218}
219
220impl Display for SemanticVersion {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
223 }
224}