1use std::cmp::Ordering;
2use std::convert::TryFrom;
3use std::fmt;
4use std::hash::{Hash, Hasher};
5use std::ops::Deref;
6
7use sea_orm::Value;
8use thiserror::Error;
9use utoipa::ToSchema;
10
11#[derive(Debug, Eq, Clone, serde::Serialize, ToSchema)]
12#[schema(value_type = String)]
13pub struct Version(String);
14
15impl<'de> serde::Deserialize<'de> for Version {
16 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
17 where
18 D: serde::Deserializer<'de>,
19 {
20 let s = <String as serde::Deserialize>::deserialize(deserializer)?;
21 semver::Version::parse(&s)
22 .map(|sv| Version(sv.to_string()))
23 .map_err(|e| serde::de::Error::custom(e.to_string()))
24 }
25}
26
27#[derive(Debug, Eq, PartialEq, Error)]
28pub enum VersionError {
29 #[error("Invalid SemVer")]
30 InvalidSemVer,
31}
32
33impl Version {
34 pub fn from_unchecked_str(version: &str) -> Self {
35 Self(version.to_string())
36 }
37 pub fn into_inner(self) -> String {
38 self.0
39 }
40
41 pub fn eq_ignoring_build(&self, other: &Version) -> bool {
45 match (semver::Version::parse(self), semver::Version::parse(other)) {
46 (Ok(a), Ok(b)) => {
47 (a.major, a.minor, a.patch, a.pre) == (b.major, b.minor, b.patch, b.pre)
48 }
49 _ => self.0 == other.0,
50 }
51 }
52}
53
54impl From<Version> for Value {
55 fn from(value: Version) -> Self {
56 Value::String(Some(value.0))
57 }
58}
59
60impl From<&Version> for Value {
61 fn from(value: &Version) -> Self {
62 Value::String(Some(value.0.clone()))
63 }
64}
65
66impl TryFrom<&str> for Version {
67 type Error = VersionError;
68
69 fn try_from(version: &str) -> Result<Self, Self::Error> {
70 Version::try_from(&version.to_string())
71 }
72}
73
74impl TryFrom<&String> for Version {
75 type Error = VersionError;
76
77 fn try_from(version: &String) -> Result<Self, Self::Error> {
78 match semver::Version::parse(version) {
79 Ok(sv) => Ok(Version(sv.to_string())),
80 Err(_) => Err(VersionError::InvalidSemVer),
81 }
82 }
83}
84
85impl AsRef<Version> for Version {
86 fn as_ref(&self) -> &Version {
87 self
88 }
89}
90
91impl Deref for Version {
92 type Target = String;
93
94 fn deref(&self) -> &Self::Target {
95 &self.0
96 }
97}
98
99impl Default for Version {
100 fn default() -> Self {
101 Version(semver::Version::new(0, 0, 0).to_string())
102 }
103}
104
105impl Ord for Version {
106 fn cmp(&self, other: &Self) -> Ordering {
107 let sv1 = semver::Version::parse(&self.to_string()).unwrap();
108 let sv2 = semver::Version::parse(&other.to_string()).unwrap();
109 sv1.cmp(&sv2)
110 }
111}
112
113impl PartialOrd for Version {
114 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
115 Some(self.cmp(other))
116 }
117}
118
119impl PartialEq for Version {
120 fn eq(&self, other: &Self) -> bool {
121 let sv1 = semver::Version::parse(&self.to_string()).unwrap();
122 let sv2 = semver::Version::parse(&other.to_string()).unwrap();
123 sv1 == sv2
124 }
125}
126
127impl Hash for Version {
128 fn hash<H: Hasher>(&self, state: &mut H) {
129 let sv = semver::Version::parse(&self.to_string()).unwrap();
130 sv.hash(state);
131 }
132}
133
134impl fmt::Display for Version {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 write!(f, "{}", self.0)
137 }
138}
139
140pub fn latest_version<'a>(versions: impl IntoIterator<Item = &'a str>) -> Option<String> {
144 versions
145 .into_iter()
146 .filter_map(|v| semver::Version::parse(v).ok())
147 .max_by(|a, b| (a.pre.is_empty(), a).cmp(&(b.pre.is_empty(), b)))
148 .map(|v| v.to_string())
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn latest_version_prefers_stable_versions() {
157 assert_eq!(
158 Some("2.0.0".to_string()),
159 latest_version(["1.0.0", "2.0.0", "1.5.0", "3.0.0-beta.1"])
160 );
161 assert_eq!(
162 Some("3.0.0-beta.2".to_string()),
163 latest_version(["3.0.0-beta.1", "3.0.0-beta.2"])
164 );
165 assert_eq!(
166 Some("1.0.0".to_string()),
167 latest_version(["1.0.0", "invalid"])
168 );
169 assert_eq!(None, latest_version([]));
170 }
171
172 #[test]
173 fn valid_package_versions() {
174 assert_eq!(Version::try_from("0.0.0").unwrap().to_string(), "0.0.0");
175 assert_eq!(Version::try_from("1.0.1").unwrap().to_string(), "1.0.1");
176 assert_eq!(
177 Version::try_from("23.123.343").unwrap().to_string(),
178 "23.123.343"
179 );
180 assert_eq!(
181 Version::try_from("2.43.3-alpha34").unwrap().to_string(),
182 "2.43.3-alpha34"
183 );
184 assert_eq!(
185 Version::try_from("0.1.1-45rdfsd-45").unwrap().to_string(),
186 "0.1.1-45rdfsd-45"
187 );
188 }
189
190 #[test]
191 fn eq_ignoring_build_ignores_only_build_metadata() {
192 let v = |s: &str| Version::try_from(s).unwrap();
193
194 assert!(v("1.0.0+a").eq_ignoring_build(&v("1.0.0+b")));
195 assert!(v("1.0.0").eq_ignoring_build(&v("1.0.0+b")));
196 assert!(v("1.0.0-rc.1+a").eq_ignoring_build(&v("1.0.0-rc.1")));
197 assert!(!v("1.0.0-rc.1").eq_ignoring_build(&v("1.0.0")));
198 assert!(!v("1.0.1+a").eq_ignoring_build(&v("1.0.0+a")));
199 }
200
201 #[test]
202 fn invalid_package_versions() {
203 assert_eq!(
204 Version::try_from("a.1.2").unwrap_err(),
205 VersionError::InvalidSemVer
206 );
207 assert_eq!(
208 Version::try_from("002.23.1").unwrap_err(),
209 VersionError::InvalidSemVer
210 );
211 assert_eq!(
212 Version::try_from("3.2fg.3").unwrap_err(),
213 VersionError::InvalidSemVer
214 );
215 assert_eq!(
216 Version::try_from("5.3.2.3").unwrap_err(),
217 VersionError::InvalidSemVer
218 );
219 }
220}