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