abi_stable/sabi_types/version.rs
1//! Types representing the version number of a library.
2
3use core_extensions::{SelfOps, StringExt};
4
5use std::{
6 error,
7 fmt::{self, Display},
8 num::ParseIntError,
9};
10
11use crate::std_types::RStr;
12
13/// The `<major>.<minor>.<patch>` version of a library,
14///
15/// # Post 1.0 major version
16///
17/// Major versions are mutually incompatible for both users and implementors.
18///
19/// Minor allow users to have a version less than or equal to that of the implementor,
20/// and disallows implementors from making changes that would break
21/// any previous minor release (with the same major number).
22///
23/// Patch cannot change the api/abi of the library at all,fixes only.
24///
25/// # Pre 1.0 version
26///
27/// Minor versions are mutually incompatible for both users and implementors.
28///
29/// Patch cannot change the api/abi of the library at all,fixes only.
30///
31/// # Example
32///
33/// ```
34/// use abi_stable::sabi_types::VersionStrings;
35///
36/// let v1_0_0 = VersionStrings::new("1.0.0").parsed().unwrap();
37/// let v1_0_5 = VersionStrings::new("1.0.5").parsed().unwrap();
38/// let v1_1_0 = VersionStrings::new("1.1.0").parsed().unwrap();
39/// let v2_0_0 = VersionStrings::new("1.0.5").parsed().unwrap();
40///
41/// assert!(v1_0_0.is_compatible(v1_0_5), "'{}' '{}'", v1_0_0, v1_0_5);
42/// assert!(v1_0_5.is_compatible(v1_1_0), "'{}' '{}'", v1_0_5, v1_1_0);
43/// assert!(!v1_1_0.is_compatible(v2_0_0), "'{}' '{}'", v1_1_0, v2_0_0);
44///
45/// ```
46#[derive(Debug, Copy, Clone, PartialEq, Eq, StableAbi)]
47#[repr(transparent)]
48pub struct VersionStrings {
49 /// The `major.minor.patch` version string
50 pub version: RStr<'static>,
51}
52
53/// The parsed (`<major>.<minor>.<patch>`) version number of a library.
54///
55/// # Post 1.0 major version
56///
57/// Major versions are mutually incompatible for both users and implementors.
58///
59/// Minor allow users to have a version less than or equal to that of the implementor,
60/// and disallows implementors from making changes that would break
61/// any previous minor release (with the same major number).
62///
63/// Patch cannot change the api/abi of the library at all,fixes only.
64///
65/// # Example
66///
67/// ```
68/// use abi_stable::sabi_types::VersionNumber;
69///
70/// let v0_1_0 = VersionNumber {
71/// major: 0,
72/// minor: 1,
73/// patch: 0,
74/// };
75/// let v0_1_5 = VersionNumber {
76/// major: 0,
77/// minor: 1,
78/// patch: 5,
79/// };
80/// let v0_1_8 = VersionNumber {
81/// major: 0,
82/// minor: 1,
83/// patch: 8,
84/// };
85/// let v0_2_0 = VersionNumber {
86/// major: 0,
87/// minor: 2,
88/// patch: 0,
89/// };
90///
91/// assert!(v0_1_0.is_compatible(v0_1_5), "'{}' '{}'", v0_1_0, v0_1_5);
92/// assert!(v0_1_5.is_compatible(v0_1_8), "'{}' '{}'", v0_1_5, v0_1_8);
93/// assert!(!v0_1_8.is_compatible(v0_2_0), "'{}' '{}'", v0_1_8, v0_2_0);
94///
95/// ```
96#[derive(Debug, Copy, Clone, PartialEq, Eq, StableAbi)]
97#[repr(C)]
98pub struct VersionNumber {
99 ///
100 pub major: u32,
101 ///
102 pub minor: u32,
103 ///
104 pub patch: u32,
105}
106
107impl VersionStrings {
108 /// Constructs a VersionStrings from a string with the
109 /// "major.minor.patch" format,where each one is a valid number.
110 ///
111 /// This does not check whether the string is correctly formatted,
112 /// that check is done inside `VersionStrings::parsed`.
113 ///
114 /// # Example
115 ///
116 /// ```
117 /// use abi_stable::sabi_types::VersionStrings;
118 ///
119 /// static VERSION: VersionStrings = VersionStrings::new("0.1.2");
120 ///
121 /// ```
122 pub const fn new(version: &'static str) -> Self {
123 Self {
124 version: RStr::from_str(version),
125 }
126 }
127
128 /// Attempts to convert a `VersionStrings` into a `VersionNumber`
129 ///
130 /// # Errors
131 ///
132 /// This returns a `ParseVersionError` if the string is not correctly formatted.
133 ///
134 /// # Example
135 ///
136 /// ```
137 /// use abi_stable::sabi_types::{VersionNumber, VersionStrings};
138 ///
139 /// static VERSION: VersionStrings = VersionStrings::new("0.1.2");
140 ///
141 /// assert_eq!(
142 /// VERSION.parsed(),
143 /// Ok(VersionNumber {
144 /// major: 0,
145 /// minor: 1,
146 /// patch: 2
147 /// })
148 /// );
149 ///
150 /// let err_version = VersionStrings::new("0.a.2.b");
151 /// assert!(err_version.parsed().is_err());
152 ///
153 /// ```
154 pub fn parsed(self) -> Result<VersionNumber, ParseVersionError> {
155 VersionNumber::new(self)
156 }
157}
158
159impl VersionNumber {
160 /// Attempts to convert a `VersionStrings` into a `VersionNumber`
161 ///
162 /// # Errors
163 ///
164 /// This returns a `ParseVersionError` if the string is not correctly formatted.
165 ///
166 /// # Example
167 ///
168 /// ```
169 /// use abi_stable::sabi_types::{VersionNumber, VersionStrings};
170 ///
171 /// static VERSION: VersionStrings = VersionStrings::new("10.5.20");
172 ///
173 /// assert_eq!(
174 /// VersionNumber::new(VERSION),
175 /// Ok(VersionNumber {
176 /// major: 10,
177 /// minor: 5,
178 /// patch: 20
179 /// })
180 /// );
181 ///
182 /// let err_version = VersionStrings::new("not a version number");
183 /// assert!(VersionNumber::new(err_version).is_err());
184 ///
185 /// ```
186 pub fn new(vn: VersionStrings) -> Result<Self, ParseVersionError> {
187 let mut iter = vn.version.splitn(3, '.');
188
189 VersionNumber {
190 major: iter
191 .next()
192 .unwrap_or("")
193 .parse()
194 .map_err(|x| ParseVersionError::new(vn, "major", x))?,
195 minor: iter
196 .next()
197 .unwrap_or("")
198 .parse()
199 .map_err(|x| ParseVersionError::new(vn, "minor", x))?,
200 patch: iter
201 .next()
202 .unwrap_or("")
203 .split_while(|x| ('0'..='9').contains(&x))
204 .find(|x| x.key)
205 .map_or("0", |x| x.str)
206 .parse()
207 .map_err(|x| ParseVersionError::new(vn, "patch", x))?,
208 }
209 .piped(Ok)
210 }
211
212 /// Whether the `self` version number is compatible with the
213 /// `library_implementor` version number.
214 ///
215 /// This uses modified semver rules where:
216 ///
217 /// - For 0.y.z ,y is interpreted as a major version,
218 /// z is interpreted as the minor version,
219 ///
220 /// - For x.y.z ,x>=1,y is interpreted as a minor version.
221 ///
222 /// - Libraries are compatible so long as they are the same
223 /// major version with a minor_version >=`self`.
224 ///
225 /// # Example
226 ///
227 /// ```
228 /// use abi_stable::sabi_types::VersionNumber;
229 ///
230 /// let v0_1_0 = VersionNumber {
231 /// major: 0,
232 /// minor: 1,
233 /// patch: 0,
234 /// };
235 /// let v0_1_5 = VersionNumber {
236 /// major: 0,
237 /// minor: 1,
238 /// patch: 5,
239 /// };
240 /// let v0_1_8 = VersionNumber {
241 /// major: 0,
242 /// minor: 1,
243 /// patch: 8,
244 /// };
245 /// let v0_2_0 = VersionNumber {
246 /// major: 0,
247 /// minor: 2,
248 /// patch: 0,
249 /// };
250 ///
251 /// assert!(v0_1_0.is_compatible(v0_1_5), "'{}' '{}'", v0_1_0, v0_1_5);
252 /// assert!(v0_1_5.is_compatible(v0_1_8), "'{}' '{}'", v0_1_5, v0_1_8);
253 /// assert!(!v0_1_8.is_compatible(v0_2_0), "'{}' '{}'", v0_1_8, v0_2_0);
254 ///
255 /// ```
256 pub const fn is_compatible(self, library_implementor: VersionNumber) -> bool {
257 if self.major == 0 && library_implementor.major == 0 {
258 self.minor == library_implementor.minor && self.patch <= library_implementor.patch
259 } else {
260 self.major == library_implementor.major && self.minor <= library_implementor.minor
261 }
262 }
263 /// Whether the `self` version number is compatible with the
264 /// library version number.
265 ///
266 /// This uses the same semver rules as cargo:
267 ///
268 /// - For 0.y.z ,y is interpreted as a major version,
269 /// z is interpreted as the minor version,
270 ///
271 /// - For x.y.z ,x>=1,y is interpreted as a minor version.
272 ///
273 /// - Libraries are compatible so long as they are the same
274 /// major version irrespective of their minor version.
275 ///
276 /// # Example
277 ///
278 /// ```
279 /// use abi_stable::sabi_types::VersionNumber;
280 ///
281 /// let v0_1_0 = VersionNumber {
282 /// major: 0,
283 /// minor: 1,
284 /// patch: 0,
285 /// };
286 /// let v0_1_5 = VersionNumber {
287 /// major: 0,
288 /// minor: 1,
289 /// patch: 5,
290 /// };
291 /// let v0_1_8 = VersionNumber {
292 /// major: 0,
293 /// minor: 1,
294 /// patch: 8,
295 /// };
296 /// let v0_2_0 = VersionNumber {
297 /// major: 0,
298 /// minor: 2,
299 /// patch: 0,
300 /// };
301 /// let v0_2_8 = VersionNumber {
302 /// major: 0,
303 /// minor: 2,
304 /// patch: 8,
305 /// };
306 /// let v1_0_0 = VersionNumber {
307 /// major: 1,
308 /// minor: 0,
309 /// patch: 0,
310 /// };
311 /// let v1_5_0 = VersionNumber {
312 /// major: 1,
313 /// minor: 5,
314 /// patch: 0,
315 /// };
316 /// let v2_0_0 = VersionNumber {
317 /// major: 2,
318 /// minor: 0,
319 /// patch: 0,
320 /// };
321 ///
322 /// fn is_compat_assert(l: VersionNumber, r: VersionNumber, are_they_compat: bool) {
323 /// assert_eq!(l.is_loosely_compatible(r), are_they_compat);
324 /// assert_eq!(r.is_loosely_compatible(l), are_they_compat);
325 /// }
326 ///
327 /// is_compat_assert(v0_1_0, v0_1_5, true);
328 /// is_compat_assert(v0_1_5, v0_1_8, true);
329 /// is_compat_assert(v1_0_0, v1_5_0, true);
330 /// is_compat_assert(v0_1_8, v0_2_0, false);
331 /// is_compat_assert(v0_2_8, v1_0_0, false);
332 /// is_compat_assert(v2_0_0, v1_0_0, false);
333 /// is_compat_assert(v2_0_0, v1_5_0, false);
334 ///
335 /// ```
336 pub const fn is_loosely_compatible(self, library_implementor: VersionNumber) -> bool {
337 if self.major == 0 && library_implementor.major == 0 {
338 self.minor == library_implementor.minor
339 } else {
340 self.major == library_implementor.major
341 }
342 }
343}
344
345impl fmt::Display for VersionNumber {
346 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
348 }
349}
350
351impl fmt::Display for VersionStrings {
352 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353 fmt::Display::fmt(&self.version, f)
354 }
355}
356
357////////////////////////////////////////////////////////////////////////////////
358////////////////////////////////////////////////////////////////////////////////
359
360/// Instantiates a [`VersionStrings`] with the
361/// major.minor.patch version of the library where it is invoked.
362///
363/// [`VersionStrings`]: ./sabi_types/version/struct.VersionStrings.html
364#[macro_export]
365macro_rules! package_version_strings {
366 () => {{
367 $crate::sabi_types::VersionStrings::new(env!("CARGO_PKG_VERSION"))
368 }};
369}
370
371////////////////////////////////////////////////////////////////////////////////
372////////////////////////////////////////////////////////////////////////////////
373
374/// When the `VersionStrings` could not be converted into a `VersionNumber`.
375#[derive(Debug, Clone, PartialEq, Eq)]
376pub struct ParseVersionError {
377 version_strings: VersionStrings,
378 which_field: &'static str,
379 parse_error: ParseIntError,
380}
381
382impl ParseVersionError {
383 const fn new(
384 version_strings: VersionStrings,
385 which_field: &'static str,
386 parse_error: ParseIntError,
387 ) -> Self {
388 Self {
389 version_strings,
390 which_field,
391 parse_error,
392 }
393 }
394
395 /// Gets back the `VersionStrings` that could not be parsed into a `VersionNumber`.
396 pub const fn version_strings(&self) -> VersionStrings {
397 self.version_strings
398 }
399}
400
401impl Display for ParseVersionError {
402 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403 writeln!(
404 f,
405 "\nInvalid version string:'{}'\nerror at the {} field:{}",
406 self.version_strings, self.which_field, self.parse_error,
407 )
408 }
409}
410
411impl error::Error for ParseVersionError {}