tc_value/
version.rs

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/// A semantic version with a major, minor, and patch number, e.g. "0.1.12"
18#[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    /// Construct an [`Id`] from this [`Version`] number.
27    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}