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
42impl From<Version> for Value {
43 fn from(value: Version) -> Self {
44 Value::String(Some(value.0))
45 }
46}
47
48impl From<&Version> for Value {
49 fn from(value: &Version) -> Self {
50 Value::String(Some(value.0.clone()))
51 }
52}
53
54impl TryFrom<&str> for Version {
55 type Error = VersionError;
56
57 fn try_from(version: &str) -> Result<Self, Self::Error> {
58 Version::try_from(&version.to_string())
59 }
60}
61
62impl TryFrom<&String> for Version {
63 type Error = VersionError;
64
65 fn try_from(version: &String) -> Result<Self, Self::Error> {
66 match semver::Version::parse(version) {
67 Ok(sv) => Ok(Version(sv.to_string())),
68 Err(_) => Err(VersionError::InvalidSemVer),
69 }
70 }
71}
72
73impl AsRef<Version> for Version {
74 fn as_ref(&self) -> &Version {
75 self
76 }
77}
78
79impl Deref for Version {
80 type Target = String;
81
82 fn deref(&self) -> &Self::Target {
83 &self.0
84 }
85}
86
87impl Default for Version {
88 fn default() -> Self {
89 Version(semver::Version::new(0, 0, 0).to_string())
90 }
91}
92
93impl Ord for Version {
94 fn cmp(&self, other: &Self) -> Ordering {
95 let sv1 = semver::Version::parse(&self.to_string()).unwrap();
96 let sv2 = semver::Version::parse(&other.to_string()).unwrap();
97 sv1.cmp(&sv2)
98 }
99}
100
101impl PartialOrd for Version {
102 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
103 Some(self.cmp(other))
104 }
105}
106
107impl PartialEq for Version {
108 fn eq(&self, other: &Self) -> bool {
109 let sv1 = semver::Version::parse(&self.to_string()).unwrap();
110 let sv2 = semver::Version::parse(&other.to_string()).unwrap();
111 sv1 == sv2
112 }
113}
114
115impl Hash for Version {
116 fn hash<H: Hasher>(&self, state: &mut H) {
117 let sv = semver::Version::parse(&self.to_string()).unwrap();
118 sv.hash(state);
119 }
120}
121
122impl fmt::Display for Version {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 write!(f, "{}", self.0)
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn valid_package_versions() {
134 assert_eq!(Version::try_from("0.0.0").unwrap().to_string(), "0.0.0");
135 assert_eq!(Version::try_from("1.0.1").unwrap().to_string(), "1.0.1");
136 assert_eq!(
137 Version::try_from("23.123.343").unwrap().to_string(),
138 "23.123.343"
139 );
140 assert_eq!(
141 Version::try_from("2.43.3-alpha34").unwrap().to_string(),
142 "2.43.3-alpha34"
143 );
144 assert_eq!(
145 Version::try_from("0.1.1-45rdfsd-45").unwrap().to_string(),
146 "0.1.1-45rdfsd-45"
147 );
148 }
149
150 #[test]
151 fn invalid_package_versions() {
152 assert_eq!(
153 Version::try_from("a.1.2").unwrap_err(),
154 VersionError::InvalidSemVer
155 );
156 assert_eq!(
157 Version::try_from("002.23.1").unwrap_err(),
158 VersionError::InvalidSemVer
159 );
160 assert_eq!(
161 Version::try_from("3.2fg.3").unwrap_err(),
162 VersionError::InvalidSemVer
163 );
164 assert_eq!(
165 Version::try_from("5.3.2.3").unwrap_err(),
166 VersionError::InvalidSemVer
167 );
168 }
169}