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 {}