cmake_package/
version.rs

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