1use std::cmp::Ordering;
2use std::fmt;
3use std::str::FromStr;
4
5use async_hash::{Digest, Hash, Output};
6use async_trait::async_trait;
7use destream::{de, en};
8use get_size::GetSize;
9use get_size_derive::*;
10use safecast::TryCastFrom;
11use serde::de::{Deserialize, Deserializer};
12use serde::ser::{Serialize, Serializer};
13
14use tc_error::*;
15use tcgeneric::Id;
16
17#[derive(Clone, Copy, Default, std::hash::Hash, Eq, PartialEq, GetSize)]
19pub struct Version {
20 major: u32,
21 minor: u32,
22 patch: u32,
23}
24
25impl Version {
26 pub fn to_id(&self) -> Id {
28 Id::try_cast_from(self.to_string(), |_| {
29 unreachable!("number failed ID validation")
30 })
31 .unwrap()
32 }
33}
34
35impl PartialOrd for Version {
36 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
37 Some(self.cmp(other))
38 }
39}
40
41impl Ord for Version {
42 fn cmp(&self, other: &Self) -> Ordering {
43 match self.major.cmp(&other.major) {
44 Ordering::Equal => match self.minor.cmp(&other.minor) {
45 Ordering::Equal => self.patch.cmp(&other.patch),
46 ordering => ordering,
47 },
48 ordering => ordering,
49 }
50 }
51}
52
53impl PartialEq<String> for Version {
54 fn eq(&self, other: &String) -> bool {
55 &self.to_string() == other
56 }
57}
58
59impl PartialEq<str> for Version {
60 fn eq(&self, other: &str) -> bool {
61 self.to_string().as_str() == other
62 }
63}
64
65impl<D: Digest> Hash<D> for Version {
66 fn hash(self) -> Output<D> {
67 Hash::<D>::hash([self.major, self.minor, self.patch])
68 }
69}
70
71impl From<(u32, u32, u32)> for Version {
72 fn from(version: (u32, u32, u32)) -> Self {
73 let (major, minor, patch) = version;
74 Self {
75 major,
76 minor,
77 patch,
78 }
79 }
80}
81
82impl FromStr for Version {
83 type Err = TCError;
84
85 fn from_str(s: &str) -> TCResult<Self> {
86 if s.find('.').is_none() {
87 return Err(TCError::unexpected(s, "a version number"));
88 }
89
90 let mut parts = s.split('.');
91
92 let major = if let Some(major) = parts.next() {
93 major.parse().map_err(|cause| {
94 TCError::unexpected(major, "a major version number").consume(cause)
95 })?
96 } else {
97 return Err(bad_request!(
98 "{} is missing missing a major version number",
99 s
100 ));
101 };
102
103 let minor = if let Some(minor) = parts.next() {
104 minor.parse().map_err(|cause| {
105 TCError::unexpected(minor, "a minor version number").consume(cause)
106 })?
107 } else {
108 return Err(bad_request!(
109 "{} is missing missing a minor version number",
110 s
111 ));
112 };
113
114 let patch = if let Some(patch) = parts.next() {
115 patch
116 .parse()
117 .map_err(|cause| TCError::unexpected(patch, "a patch number").consume(cause))?
118 } else {
119 return Err(bad_request!("{} is missing missing a patch number", s));
120 };
121
122 if parts.next().is_some() {
123 return Err(bad_request!("invalid semantic version number: {}", s));
124 }
125
126 Ok(Self {
127 major,
128 minor,
129 patch: patch,
130 })
131 }
132}
133
134impl TryCastFrom<&str> for Version {
135 fn can_cast_from(value: &&str) -> bool {
136 Self::from_str(value).is_ok()
137 }
138
139 fn opt_cast_from(value: &str) -> Option<Self> {
140 Self::from_str(value).ok()
141 }
142}
143
144impl TryCastFrom<Id> for Version {
145 fn can_cast_from(value: &Id) -> bool {
146 Self::from_str(value.as_str()).is_ok()
147 }
148
149 fn opt_cast_from(value: Id) -> Option<Self> {
150 Self::from_str(value.as_str()).ok()
151 }
152}
153
154impl From<Version> for Id {
155 fn from(version: Version) -> Id {
156 version.to_string().parse().expect("version id")
157 }
158}
159
160impl<'de> Deserialize<'de> for Version {
161 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
162 let version: &str = Deserialize::deserialize(deserializer)?;
163 version.parse().map_err(serde::de::Error::custom)
164 }
165}
166
167impl Serialize for Version {
168 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
169 self.to_string().serialize(serializer)
170 }
171}
172
173#[async_trait]
174impl de::FromStream for Version {
175 type Context = ();
176
177 async fn from_stream<D: de::Decoder>(cxt: (), decoder: &mut D) -> Result<Self, D::Error> {
178 let version = String::from_stream(cxt, decoder).await?;
179 version.parse().map_err(de::Error::custom)
180 }
181}
182
183impl<'en> en::IntoStream<'en> for Version {
184 fn into_stream<E: en::Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
185 self.to_string().into_stream(encoder)
186 }
187}
188
189impl<'en> en::ToStream<'en> for Version {
190 fn to_stream<E: en::Encoder<'en>>(&self, encoder: E) -> Result<E::Ok, E::Error> {
191 en::IntoStream::into_stream(self.to_string(), encoder)
192 }
193}
194
195impl fmt::Debug for Version {
196 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
197 fmt::Display::fmt(self, f)
198 }
199}
200
201impl fmt::Display for Version {
202 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
203 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
204 }
205}