Skip to main content

rustversion_detect/
version.rs

1//! Defines the rust version types.
2
3use core::fmt::{self, Display, Formatter};
4use core::num::ParseIntError;
5use core::str::FromStr;
6
7use crate::date::Date;
8
9/// Specifies a specific stable version, like `1.48`.
10#[derive(Copy, Clone, Debug, Eq, PartialEq)]
11pub struct StableVersionSpec {
12    /// The major version
13    pub major: u32,
14    /// The minor version
15    pub minor: u32,
16    /// The patch version.
17    ///
18    /// If this is `None`, it will match any patch version.
19    pub patch: Option<u32>,
20}
21impl StableVersionSpec {
22    /// Specify a minor version like `1.32`.
23    ///
24    /// # Panics
25    /// Panics if the major version is not `1`.
26    #[inline]
27    #[must_use]
28    pub fn minor(major: u32, minor: u32) -> Self {
29        check_major_version(major);
30        StableVersionSpec {
31            major,
32            minor,
33            patch: None,
34        }
35    }
36
37    /// Specify a patch version like `1.32.4`.
38    ///
39    /// # Panics
40    /// Panics if the major version is not `1`.
41    #[inline]
42    #[must_use]
43    pub fn patch(major: u32, minor: u32, patch: u32) -> Self {
44        check_major_version(major);
45        StableVersionSpec {
46            major,
47            minor,
48            patch: Some(patch),
49        }
50    }
51
52    /// Convert this specification into a concrete [`RustVersion`].
53    ///
54    /// If the patch version is not specified,
55    /// it is assumed to be zero.
56    #[inline]
57    #[must_use]
58    pub fn to_version(&self) -> RustVersion {
59        RustVersion::stable(self.major, self.minor, self.patch.unwrap_or(0))
60    }
61}
62impl FromStr for StableVersionSpec {
63    type Err = StableVersionParseError;
64
65    fn from_str(s: &str) -> Result<Self, Self::Err> {
66        let mut iter = s.split('.');
67        let major = iter
68            .next()
69            .ok_or(StableVersionParseError::BadNumberParts)?
70            .parse::<u32>()?;
71        let minor = iter
72            .next()
73            .ok_or(StableVersionParseError::BadNumberParts)?
74            .parse::<u32>()?;
75        let patch = match iter.next() {
76            Some(patch_text) => Some(patch_text.parse::<u32>()?),
77            None => None,
78        };
79        if iter.next().is_some() {
80            return Err(StableVersionParseError::BadNumberParts);
81        }
82        if major != 1 {
83            return Err(StableVersionParseError::InvalidMajorVersion);
84        }
85        Ok(StableVersionSpec {
86            major,
87            minor,
88            patch,
89        })
90    }
91}
92
93/// An error while parsing a [`StableVersionSpec`].
94///
95/// The specifics of this error are implementation-dependent.
96#[derive(Clone, Debug)]
97pub enum StableVersionParseError {
98    #[doc(hidden)]
99    InvalidNumber(ParseIntError),
100    #[doc(hidden)]
101    BadNumberParts,
102    #[doc(hidden)]
103    InvalidMajorVersion,
104}
105impl From<ParseIntError> for StableVersionParseError {
106    #[inline]
107    fn from(cause: ParseIntError) -> Self {
108        StableVersionParseError::InvalidNumber(cause)
109    }
110}
111
112/// Show the specification in a manner consistent with the `spec!` macro.
113impl Display for StableVersionSpec {
114    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
115        write!(f, "{}.{}", self.major, self.minor)?;
116        if let Some(patch) = self.patch {
117            write!(f, ".{}", patch)?;
118        }
119        Ok(())
120    }
121}
122
123/// Indicates the rust version.
124#[derive(Copy, Clone, Debug, Eq, PartialEq)]
125pub struct RustVersion {
126    /// The major version.
127    ///
128    /// Should always be one.
129    pub major: u32,
130    /// The minor version of rust.
131    pub minor: u32,
132    /// The patch version of the rust compiler.
133    pub patch: u32,
134    /// The channel of the rust compiler.
135    pub channel: Channel,
136}
137impl RustVersion {
138    /// Create a stable version with the specified combination of major, minor, and patch.
139    ///
140    /// The major version must be 1.0.
141    #[inline]
142    #[must_use]
143    pub fn stable(major: u32, minor: u32, patch: u32) -> RustVersion {
144        check_major_version(major);
145        RustVersion {
146            major,
147            minor,
148            patch,
149            channel: Channel::Stable,
150        }
151    }
152
153    /// Check if this version is after the specified stable minor version.
154    ///
155    /// The patch version is unspecified and will be ignored.
156    ///
157    /// This is a shorthand for calling [`Self::is_since_stable`] with a minor version
158    /// spec created with [`StableVersionSpec::minor`].
159    ///
160    /// The major version must always be one, or a panic could happen.
161    ///
162    /// ## Example
163    /// ```
164    /// # use rustversion_detect::RustVersion;
165    ///
166    /// assert!(RustVersion::stable(1, 32, 2).is_since_minor_version(1, 32));
167    /// assert!(RustVersion::stable(1, 48, 0).is_since_minor_version(1, 40));
168    /// ```
169    #[inline]
170    #[must_use]
171    pub fn is_since_minor_version(&self, major: u32, minor: u32) -> bool {
172        self.is_since_stable(StableVersionSpec::minor(major, minor))
173    }
174
175    /// Check if this version is after the specified stable patch version.
176    ///
177    /// This is a shorthand for calling [`Self::is_since_stable`] with a patch version
178    /// spec created with [`StableVersionSpec::patch`].
179    ///
180    /// The major version must always be one, or a panic could happen.
181    ///
182    /// ## Example
183    /// ```
184    /// # use rustversion_detect::RustVersion;
185    ///
186    /// assert!(RustVersion::stable(1, 32, 2).is_since_patch_version(1, 32, 1));
187    /// assert!(RustVersion::stable(1, 48, 0).is_since_patch_version(1, 40, 5));
188    /// ```
189    #[inline]
190    #[must_use]
191    pub fn is_since_patch_version(&self, major: u32, minor: u32, patch: u32) -> bool {
192        self.is_since_stable(StableVersionSpec::patch(major, minor, patch))
193    }
194
195    /// Check if this version is after the given [stable version spec](StableVersionSpec).
196    ///
197    /// In general, the [`Self::is_since_minor_version`] and [`Self::is_since_patch_version`]
198    /// helper methods are preferable.
199    ///
200    /// This ignores the channel.
201    ///
202    /// The negation of [`Self::is_before_stable`].
203    ///
204    /// Behavior is (mostly) equivalent to `#[rustversion::since($spec)]`
205    ///
206    /// ## Example
207    /// ```
208    /// # use rustversion_detect::{RustVersion, StableVersionSpec};
209    ///
210    /// assert!(RustVersion::stable(1, 32, 2).is_since_stable(StableVersionSpec::minor(1, 32)));
211    /// assert!(RustVersion::stable(1, 48, 0).is_since_stable(StableVersionSpec::patch(1, 32, 7)))
212    /// ```
213    #[inline]
214    #[must_use]
215    pub fn is_since_stable(&self, spec: StableVersionSpec) -> bool {
216        self.major > spec.major
217            || (self.major == spec.major
218                && (self.minor > spec.minor
219                    || (self.minor == spec.minor
220                        && match spec.patch {
221                            None => true, // missing spec always matches
222                            Some(patch_spec) => self.patch >= patch_spec,
223                        })))
224    }
225
226    /// Check if the version is less than the given [stable version spec](StableVersionSpec).
227    ///
228    /// This ignores the channel.
229    ///
230    /// In general, the [`Self::is_before_minor_version`] and [`Self::is_before_patch_version`]
231    /// helper methods are preferable.
232    ///
233    /// The negation of [`Self::is_since_stable`].
234    ///
235    /// Behavior is (mostly) equivalent to `#[rustversion::before($spec)]`
236    #[inline]
237    #[must_use]
238    pub fn is_before_stable(&self, spec: StableVersionSpec) -> bool {
239        !self.is_since_stable(spec)
240    }
241
242    /// Check if this version is before the specified stable minor version.
243    ///
244    /// The patch version is unspecified and will be ignored.
245    ///
246    /// This is a shorthand for calling [`Self::is_before_stable`] with a minor version
247    /// spec created with [`StableVersionSpec::minor`].
248    ///
249    /// The major version must always be one, or a panic could happen.
250    #[inline]
251    #[must_use]
252    pub fn is_before_minor_version(&self, major: u32, minor: u32) -> bool {
253        self.is_before_stable(StableVersionSpec::minor(major, minor))
254    }
255
256    /// Check if this version is before the specified stable patch version.
257    ///
258    /// This is a shorthand for calling [`Self::is_before_stable`] with a patch version
259    /// spec created with [`StableVersionSpec::patch`].
260    ///
261    /// The major version must always be one, or a panic could happen.
262    #[inline]
263    #[must_use]
264    pub fn is_before_patch_version(&self, major: u32, minor: u32, patch: u32) -> bool {
265        self.is_before_stable(StableVersionSpec::patch(major, minor, patch))
266    }
267
268    /// If this version is a nightly version after the specified start date.
269    ///
270    /// Stable and beta versions are always considered before every nightly versions.
271    /// Development versions are considered after every nightly version.
272    ///
273    /// The negation of [`Self::is_before_nightly`].
274    ///
275    /// Behavior is (mostly) equivalent to `#[rustversion::since($date)]`
276    ///
277    /// See also [`Date::is_since`].
278    #[inline]
279    #[must_use]
280    pub fn is_since_nightly(&self, start: Date) -> bool {
281        match self.channel {
282            Channel::Nightly { date } => date.is_since(start),
283            Channel::Stable | Channel::Beta => false, // before every nightly
284            Channel::Development => true,             // after every nightly version
285            Channel::__NonExhaustive => unreachable!(),
286        }
287    }
288
289    /// If this version comes before the nightly version with the specified start date.
290    ///
291    /// Stable and beta versions are always considered before every nightly versions.
292    /// Development versions are considered after every nightly version.
293    ///
294    /// The negation of [`Self::is_since_nightly`].
295    ///
296    /// See also [`Date::is_before`].
297    #[inline]
298    #[must_use]
299    pub fn is_before_nightly(&self, start: Date) -> bool {
300        match self.channel {
301            Channel::Nightly { date } => date <= start,
302            Channel::Stable | Channel::Beta => false, // before every nightly
303            Channel::Development => true,             // after every nightly version
304            Channel::__NonExhaustive => unreachable!(),
305        }
306    }
307
308    /// Check if this is a nightly compiler version.
309    #[inline]
310    #[must_use]
311    pub fn is_nightly(&self) -> bool {
312        self.channel.is_nightly()
313    }
314
315    /// Check if this is a stable compiler version.
316    #[inline]
317    #[must_use]
318    pub fn is_stable(&self) -> bool {
319        self.channel.is_stable()
320    }
321
322    /// Check if this is a beta compiler version.
323    #[inline]
324    #[must_use]
325    pub fn is_beta(&self) -> bool {
326        self.channel.is_beta()
327    }
328
329    /// Check if this is a development compiler version.
330    #[inline]
331    #[must_use]
332    pub fn is_development(&self) -> bool {
333        self.channel.is_development()
334    }
335}
336
337impl From<StableVersionSpec> for RustVersion {
338    #[inline]
339    fn from(value: StableVersionSpec) -> Self {
340        value.to_version()
341    }
342}
343
344/// Displays the version in a manner similar to `rustc --version`.
345///
346/// The format here is not stable and may change in the future.
347impl Display for RustVersion {
348    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
350        match self.channel {
351            Channel::Stable => Ok(()), // nothing
352            Channel::Beta => f.write_str("-beta"),
353            Channel::Nightly { ref date } => {
354                write!(f, "-nightly ({})", date)
355            }
356            Channel::Development => f.write_str("-dev"),
357            Channel::__NonExhaustive => unreachable!(),
358        }
359    }
360}
361
362/// The [channel] of the rust compiler release.
363///
364/// [channel]: https://rust-lang.github.io/rustup/concepts/channels.html
365#[derive(Copy, Clone, Debug, Eq, PartialEq)]
366pub enum Channel {
367    /// A stable compiler version.
368    Stable,
369    /// A beta compiler version.
370    Beta,
371    /// A nightly compiler version.
372    Nightly {
373        /// The date that the compiler was released.
374        date: Date,
375    },
376    /// A development compiler version.
377    ///
378    /// These are compiled directly instead of distributed through [rustup](https://rustup.rs).
379    Development,
380    #[doc(hidden)]
381    __NonExhaustive,
382}
383impl Channel {
384    /// Check if this is the nightly channel.
385    #[inline]
386    #[must_use]
387    pub fn is_nightly(&self) -> bool {
388        // NOTE: Can't use matches! because of minimum rust version
389        match *self {
390            Channel::Nightly { .. } => true,
391            _ => false,
392        }
393    }
394
395    /// Check if this is the stable channel.
396    #[inline]
397    #[must_use]
398    pub fn is_stable(&self) -> bool {
399        match *self {
400            Channel::Stable => true,
401            _ => false,
402        }
403    }
404
405    /// Check if this is the beta channel.
406    #[inline]
407    #[must_use]
408    pub fn is_beta(&self) -> bool {
409        match *self {
410            Channel::Beta => true,
411            _ => false,
412        }
413    }
414
415    /// Check if this is the development channel.
416    #[inline]
417    #[must_use]
418    pub fn is_development(&self) -> bool {
419        match *self {
420            Channel::Development => true,
421            _ => false,
422        }
423    }
424}
425
426#[inline]
427fn check_major_version(major: u32) {
428    assert_eq!(major, 1, "Major version must be 1.*");
429}
430
431#[cfg(test)]
432mod test {
433    use super::{RustVersion, StableVersionSpec};
434
435    // (before, after)
436    fn versions() -> Vec<(RustVersion, RustVersion)> {
437        vec![
438            (RustVersion::stable(1, 7, 8), RustVersion::stable(1, 89, 0)),
439            (RustVersion::stable(1, 18, 0), RustVersion::stable(1, 80, 3)),
440        ]
441    }
442
443    #[cfg(test)]
444    impl RustVersion {
445        #[inline]
446        pub(crate) fn to_spec(self) -> StableVersionSpec {
447            StableVersionSpec::patch(self.major, self.minor, self.patch)
448        }
449    }
450
451    #[test]
452    fn test_before_after() {
453        for (before, after) in versions() {
454            assert!(
455                before.is_before_stable(after.to_spec()),
456                "{} & {}",
457                before,
458                after
459            );
460            assert!(
461                after.is_since_stable(before.to_spec()),
462                "{} & {}",
463                before,
464                after
465            );
466        }
467    }
468}