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