1use std::convert::TryFrom;
2use std::fmt;
3use std::str::FromStr;
4
5use sea_orm::Value;
6use thiserror::Error;
7use utoipa::ToSchema;
8
9#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, ToSchema)]
13#[schema(value_type = String)]
14pub struct Version(semver::Version);
15
16impl serde::Serialize for Version {
17 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
18 where
19 S: serde::Serializer,
20 {
21 serializer.collect_str(&self.0)
22 }
23}
24
25impl<'de> serde::Deserialize<'de> for Version {
26 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
27 where
28 D: serde::Deserializer<'de>,
29 {
30 let s = <String as serde::Deserialize>::deserialize(deserializer)?;
31 Version::try_from(s.as_str()).map_err(serde::de::Error::custom)
32 }
33}
34
35#[derive(Debug, Eq, PartialEq, Error)]
36pub enum VersionError {
37 #[error("Invalid SemVer")]
38 InvalidSemVer,
39}
40
41impl Version {
42 pub fn eq_ignoring_build(&self, other: &Version) -> bool {
46 let (a, b) = (&self.0, &other.0);
47 (a.major, a.minor, a.patch, &a.pre) == (b.major, b.minor, b.patch, &b.pre)
48 }
49}
50
51impl From<Version> for Value {
52 fn from(value: Version) -> Self {
53 Value::String(Some(value.to_string()))
54 }
55}
56
57impl From<&Version> for Value {
58 fn from(value: &Version) -> Self {
59 Value::String(Some(value.to_string()))
60 }
61}
62
63impl TryFrom<&str> for Version {
64 type Error = VersionError;
65
66 fn try_from(version: &str) -> Result<Self, Self::Error> {
67 semver::Version::parse(version)
68 .map(Version)
69 .map_err(|_| VersionError::InvalidSemVer)
70 }
71}
72
73impl TryFrom<&String> for Version {
74 type Error = VersionError;
75
76 fn try_from(version: &String) -> Result<Self, Self::Error> {
77 Version::try_from(version.as_str())
78 }
79}
80
81impl TryFrom<String> for Version {
82 type Error = VersionError;
83
84 fn try_from(version: String) -> Result<Self, Self::Error> {
85 Version::try_from(version.as_str())
86 }
87}
88
89impl FromStr for Version {
90 type Err = VersionError;
91
92 fn from_str(version: &str) -> Result<Self, Self::Err> {
93 Version::try_from(version)
94 }
95}
96
97impl AsRef<Version> for Version {
98 fn as_ref(&self) -> &Version {
99 self
100 }
101}
102
103impl Default for Version {
104 fn default() -> Self {
105 Version(semver::Version::new(0, 0, 0))
106 }
107}
108
109impl fmt::Debug for Version {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 f.debug_tuple("Version").field(&self.to_string()).finish()
112 }
113}
114
115impl fmt::Display for Version {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 fmt::Display::fmt(&self.0, f)
118 }
119}
120
121pub fn latest_version<'a>(versions: impl IntoIterator<Item = &'a str>) -> Option<String> {
125 versions
126 .into_iter()
127 .filter_map(|v| semver::Version::parse(v).ok())
128 .max_by(|a, b| (a.pre.is_empty(), a).cmp(&(b.pre.is_empty(), b)))
129 .map(|v| v.to_string())
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn latest_version_prefers_stable_versions() {
138 assert_eq!(
139 Some("2.0.0".to_string()),
140 latest_version(["1.0.0", "2.0.0", "1.5.0", "3.0.0-beta.1"])
141 );
142 assert_eq!(
143 Some("3.0.0-beta.2".to_string()),
144 latest_version(["3.0.0-beta.1", "3.0.0-beta.2"])
145 );
146 assert_eq!(
147 Some("1.0.0".to_string()),
148 latest_version(["1.0.0", "invalid"])
149 );
150 assert_eq!(None, latest_version([]));
151 }
152
153 #[test]
154 fn valid_package_versions() {
155 assert_eq!(Version::try_from("0.0.0").unwrap().to_string(), "0.0.0");
156 assert_eq!(Version::try_from("1.0.1").unwrap().to_string(), "1.0.1");
157 assert_eq!(
158 Version::try_from("23.123.343").unwrap().to_string(),
159 "23.123.343"
160 );
161 assert_eq!(
162 Version::try_from("2.43.3-alpha34").unwrap().to_string(),
163 "2.43.3-alpha34"
164 );
165 assert_eq!(
166 Version::try_from("0.1.1-45rdfsd-45").unwrap().to_string(),
167 "0.1.1-45rdfsd-45"
168 );
169 }
170
171 #[test]
172 fn eq_ignoring_build_ignores_only_build_metadata() {
173 let v = |s: &str| Version::try_from(s).unwrap();
174
175 assert!(v("1.0.0+a").eq_ignoring_build(&v("1.0.0+b")));
176 assert!(v("1.0.0").eq_ignoring_build(&v("1.0.0+b")));
177 assert!(v("1.0.0-rc.1+a").eq_ignoring_build(&v("1.0.0-rc.1")));
178 assert!(!v("1.0.0-rc.1").eq_ignoring_build(&v("1.0.0")));
179 assert!(!v("1.0.1+a").eq_ignoring_build(&v("1.0.0+a")));
180 }
181
182 #[test]
183 fn invalid_package_versions() {
184 assert_eq!(
185 Version::try_from("a.1.2").unwrap_err(),
186 VersionError::InvalidSemVer
187 );
188 assert_eq!(
189 Version::try_from("002.23.1").unwrap_err(),
190 VersionError::InvalidSemVer
191 );
192 assert_eq!(
193 Version::try_from("3.2fg.3").unwrap_err(),
194 VersionError::InvalidSemVer
195 );
196 assert_eq!(
197 Version::try_from("5.3.2.3").unwrap_err(),
198 VersionError::InvalidSemVer
199 );
200 }
201
202 #[test]
203 fn versions_are_ordered_like_semver() {
204 let v = |s: &str| Version::try_from(s).unwrap();
205
206 assert!(v("1.0.0-alpha") < v("1.0.0-alpha.1"));
207 assert!(v("1.0.0-alpha.1") < v("1.0.0-beta"));
208 assert!(v("1.0.0-rc.1") < v("1.0.0"));
209 assert!(v("1.0.0") < v("1.0.1"));
210 assert!(v("1.9.0") < v("1.10.0"));
211 assert!(v("1.0.0") < v("1.0.0+build"));
212 assert!(v("1.0.0+a") < v("1.0.0+b"));
213 }
214
215 #[test]
216 fn versions_with_different_build_metadata_are_not_equal() {
217 let v = |s: &str| Version::try_from(s).unwrap();
218
219 assert_eq!(v("1.0.0+a"), v("1.0.0+a"));
220 assert_ne!(v("1.0.0+a"), v("1.0.0+b"));
221 assert_ne!(v("1.0.0"), v("1.0.0+a"));
222 assert_ne!(v("1.0.0"), v("1.0.0-rc.1"));
223 }
224
225 #[test]
226 fn equal_versions_have_equal_hashes() {
227 use std::collections::HashSet;
228
229 let v = |s: &str| Version::try_from(s).unwrap();
230 let set: HashSet<Version> = [v("1.0.0"), v("1.0.0"), v("1.0.0+a"), v("1.0.0-rc.1")]
231 .into_iter()
232 .collect();
233
234 assert_eq!(set.len(), 3);
235 assert!(set.contains(&v("1.0.0+a")));
236 assert!(!set.contains(&v("1.0.0+b")));
237 }
238
239 #[test]
240 fn version_keeps_its_string_form() {
241 for s in [
242 "0.0.0",
243 "1.2.3",
244 "1.0.0-alpha.1",
245 "1.0.0+build",
246 "1.0.0-rc.1+build.5",
247 "2.43.3-alpha34",
248 "1.0.0+001",
249 ] {
250 let version = Version::try_from(s).unwrap();
251 assert_eq!(version.to_string(), s);
252 let json = serde_json::to_string(&version).unwrap();
253 assert_eq!(json, format!("\"{s}\""));
254 assert_eq!(serde_json::from_str::<Version>(&json).unwrap(), version);
255 assert_eq!(Value::from(&version), Value::String(Some(s.to_string())));
256 }
257 }
258
259 #[test]
260 fn deserializing_an_invalid_version_fails() {
261 assert!(serde_json::from_str::<Version>("\"1.0\"").is_err());
262 assert!(serde_json::from_str::<Version>("\"invalid\"").is_err());
263 }
264}