cmake_package/
version.rs

1// SPDX-FileCopyrightText: 2024 Daniel Vrátil <dvratil@kde.org>
2//
3// SPDX-License-Identifier: MIT
4
5#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, serde::Deserialize)]
6#[serde(rename_all="UPPERCASE")]
7pub struct Version {
8    pub major: u32,
9    pub minor: u32,
10    pub patch: u32,
11}
12
13#[derive(Debug, Clone, PartialEq)]
14pub enum VersionError {
15    InvalidVersion,
16    VersionTooOld(Version),
17}
18
19impl Version {
20    pub fn parse(version: &str) -> Result<Version, VersionError> {
21        let parts: Vec<&str> = version.split(&['.', '-']).collect();
22        if parts.is_empty() {
23            return Err(VersionError::InvalidVersion);
24        }
25
26        Ok(Version {
27            major: parts[0].parse().or(Err(VersionError::InvalidVersion))?,
28            minor: if parts.len() > 1 {
29                parts[1].parse().or(Err(VersionError::InvalidVersion))?
30            } else {
31                0
32            },
33            patch: if parts.len() > 2 {
34                parts[2].parse().or(Err(VersionError::InvalidVersion))?
35            } else {
36                0
37            },
38        })
39    }
40}
41
42impl TryInto<Version> for &str {
43    type Error = VersionError;
44
45    fn try_into(self) -> Result<Version, Self::Error> {
46        Version::parse(self)
47    }
48}
49
50impl TryInto<Version> for String {
51    type Error = VersionError;
52
53    fn try_into(self) -> Result<Version, Self::Error> {
54        Version::parse(&self)
55    }
56}
57
58impl From<Version> for String {
59    fn from(value: Version) -> Self {
60        format!("{}.{}.{}", value.major, value.minor, value.patch)
61    }
62}
63
64impl std::fmt::Display for Version {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
67    }
68}
69
70#[cfg(test)]
71mod testing {
72    use super::*;
73
74    #[test]
75    fn test_version_parse_valid() {
76        assert_eq!(
77            Version::parse("1.2.3").unwrap(),
78            Version {
79                major: 1,
80                minor: 2,
81                patch: 3
82            }
83        );
84        assert_eq!(
85            Version::parse("1.2").unwrap(),
86            Version {
87                major: 1,
88                minor: 2,
89                patch: 0
90            }
91        );
92        assert_eq!(
93            Version::parse("1").unwrap(),
94            Version {
95                major: 1,
96                minor: 0,
97                patch: 0
98            }
99        );
100        assert_eq!(
101            Version::parse("4.0.20250530-gab5866c").unwrap(),
102            Version {
103                major: 4,
104                minor: 0,
105                patch: 20250530
106            }
107        )
108    }
109
110    #[test]
111    fn test_version_parse_invalid() {
112        assert!(Version::parse("").is_err());
113        assert!(Version::parse("1.2.3:4").is_err());
114        assert!(Version::parse("a.b.c").is_err());
115    }
116
117    #[test]
118    fn test_version_into_string() {
119        let version = Version {
120            major: 1,
121            minor: 2,
122            patch: 3,
123        };
124        let version_str: String = version.into();
125        assert_eq!(version_str, "1.2.3");
126    }
127
128    #[test]
129    fn test_version_partial_ord() {
130        let v1 = Version {
131            major: 1,
132            minor: 0,
133            patch: 0,
134        };
135        let v2 = Version {
136            major: 1,
137            minor: 1,
138            patch: 0,
139        };
140        let v3 = Version {
141            major: 1,
142            minor: 1,
143            patch: 1,
144        };
145
146        let v4 = Version {
147            major: 2,
148            minor: 0,
149            patch: 0,
150        };
151
152        assert!(v1 < v2);
153        assert!(v1 <= v2);
154        assert!(v2 < v3);
155        assert!(v2 <= v3);
156        assert!(v1 < v3);
157        assert!(v1 <= v3);
158        assert!(v3 > v2);
159        assert!(v3 >= v2);
160        assert!(v2 > v1);
161        assert!(v2 >= v1);
162        assert!(v3 > v1);
163        assert!(v3 >= v1);
164        assert!(v1 < v4);
165        assert!(v1 <= v4);
166        assert!(v4 > v1);
167        assert!(v4 >= v1);
168        assert!(v2 < v4);
169        assert!(v2 <= v4);
170        assert!(v4 > v2);
171        assert!(v4 >= v2);
172        assert!(v3 < v4);
173        assert!(v3 <= v4);
174        assert!(v4 > v3);
175        assert!(v4 >= v3);
176    }
177
178    #[test]
179    fn test_version_partial_eq() {
180        let v1 = Version {
181            major: 1,
182            minor: 0,
183            patch: 0,
184        };
185        let v2 = Version {
186            major: 1,
187            minor: 0,
188            patch: 0,
189        };
190        let v3 = Version {
191            major: 1,
192            minor: 1,
193            patch: 0,
194        };
195
196        assert_eq!(v1, v2);
197        assert_ne!(v1, v3);
198    }
199
200    #[test]
201    fn test_version_try_into() {
202        let version_str = "1.2.3";
203        let version: Version = version_str.try_into().unwrap();
204        assert_eq!(
205            version,
206            Version {
207                major: 1,
208                minor: 2,
209                patch: 3
210            }
211        );
212
213        let version_string = String::from("1.2.3");
214        let version: Version = version_string.try_into().unwrap();
215        assert_eq!(
216            version,
217            Version {
218                major: 1,
219                minor: 2,
220                patch: 3
221            }
222        );
223    }
224
225    #[test]
226    fn test_display() {
227        let version = Version {
228            major: 1,
229            minor: 2,
230            patch: 3,
231        };
232        assert_eq!(format!("{}", version), "1.2.3");
233    }
234}