Skip to main content

rust_release/
lib.rs

1//! # rust-release
2//!
3//! The [`rust-release`] crate defines a set of types which model a Rust release.
4//!
5//! This project is part of the [`rust-releases`] and [`cargo-msrv`] projects.
6//!
7//! In case you have a feature request, question, bug, or have another reason to
8//! contact the developers, please create a new issue at the `rust-releases`
9//! [`repository`].
10//!
11//! [`rust-releases`]: https://github.com/foresterre/rust-releases
12//! [`cargo-msrv`]: https://github.com/foresterre/cargo-msrv
13//! [`repository`]: https://github.com/foresterre/rust-releases/issues
14#![warn(clippy::all)]
15#![deny(missing_docs)]
16#![deny(unsafe_code)]
17
18// exports
19pub use rust_toolchain::channel::{Beta, Nightly, Stable};
20use std::cmp;
21use std::fmt::Debug;
22
23/// A module for an unrefined Date type, solely used as a version number.
24///
25/// Do not use as your date type!
26pub mod date {
27    pub use rust_toolchain::Date;
28}
29/// Describes toolchains in so far they're relevant to a release
30pub mod toolchain {
31    pub use rust_toolchain::{Channel, Component, RustVersion, Target, Toolchain};
32}
33
34/// Describes the version of a release
35pub mod version;
36
37/// Type to model a Rust release.
38///
39/// # PartialEq, Eq, Ord, PartialOrd
40///
41/// With respect to the PartialEq, Eq, PartialOrd and Ord traits, a [`RustRelease`]
42/// `a` is equal, less, or greater than a [`RustRelease`] `b` iff respectively the
43/// `a.version` field is equal, less, or greater than `b.version`.
44// The fields are have a `pub` privacy so they can be pattern patched on
45#[derive(Clone, Debug)]
46pub struct RustRelease<V: Debug, C = ()> {
47    /// The version of a [`RustRelease`].
48    ///
49    /// The versioning scheme depends on the channel, which is why the version
50    /// type is a generic. In this library, the `V` is always substituted by one
51    /// of the following types: [`Stable`], [`Beta`] or [`Nightly`].
52    ///
53    /// [`Stable`] and [`Beta`] carry a semver version number, while [`Nightly`]
54    /// is versioned by a date.
55    pub version: V,
56    /// The release date of the release.
57    ///
58    /// The field is optional, because the value may be absent from a data source.
59    pub release_date: Option<date::Date>,
60    /// The toolchains associated with the release.
61    ///
62    /// The field may be empty if toolchains were absent from a data source.
63    pub toolchains: Vec<toolchain::Toolchain>,
64    /// Arbitrary extra data
65    pub context: C,
66}
67
68impl<V: PartialEq + Debug, C> PartialEq for RustRelease<V, C> {
69    fn eq(&self, other: &Self) -> bool {
70        self.version.eq(&other.version)
71    }
72}
73
74impl<V: Eq + Debug, C> Eq for RustRelease<V, C> {}
75
76impl<V: PartialOrd + Debug, C> PartialOrd for RustRelease<V, C> {
77    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
78        self.version.partial_cmp(&other.version)
79    }
80}
81
82impl<V: Ord + Debug, C> Ord for RustRelease<V, C> {
83    fn cmp(&self, other: &Self) -> cmp::Ordering {
84        self.version.cmp(&other.version)
85    }
86}
87
88impl<V: Debug> RustRelease<V, ()> {
89    /// Create a new RustRelease instance using a version, optionally
90    /// a release date, and an iterator of toolchains.
91    pub fn new(
92        version: V,
93        release_date: Option<rust_toolchain::Date>,
94        toolchains: impl IntoIterator<Item = toolchain::Toolchain>,
95    ) -> Self {
96        Self {
97            version,
98            release_date,
99            toolchains: toolchains.into_iter().collect(),
100            context: (),
101        }
102    }
103}
104
105impl<V: Debug, C> RustRelease<V, C> {
106    /// Create a new RustRelease instance using a version, optionally
107    /// a release date, an iterator of toolchains and an "arbitrary" context.
108    ///
109    /// The context is can contain any additional data you want to store with the previously mentioned
110    /// data fields, i.e. the version, release date and toolchains. For example, you could add a struct
111    /// which contains metadata about when and where the data was fetched from, or which contains
112    /// checksums or signatures.
113    pub fn new_with_context(
114        version: V,
115        release_date: Option<rust_toolchain::Date>,
116        toolchains: impl IntoIterator<Item = toolchain::Toolchain>,
117        context: C,
118    ) -> Self {
119        Self {
120            version,
121            release_date,
122            toolchains: toolchains.into_iter().collect(),
123            context,
124        }
125    }
126    /// A shared reference to version of a release.
127    ///
128    /// # See also
129    ///
130    /// Commonly `V` is parameterized by one of these:
131    ///
132    /// * [`Stable`]
133    /// * [`Beta`]
134    /// * [`Nightly`]
135    pub fn version(&self) -> &V {
136        &self.version
137    }
138
139    /// An exclusive reference to version of a release.
140    ///
141    /// # See also
142    ///
143    /// Commonly `V` is parameterized by one of these:
144    ///
145    /// * [`Stable`]
146    /// * [`Beta`]
147    /// * [`Nightly`]
148    pub fn version_mut(&mut self) -> &mut V {
149        &mut self.version
150    }
151
152    /// A shared reference to the release date of a release, if set.
153    pub fn release_date(&self) -> Option<&date::Date> {
154        self.release_date.as_ref()
155    }
156
157    /// An exclusive reference to the release date of a release, if set.
158    pub fn release_date_mut(&mut self) -> Option<&mut date::Date> {
159        self.release_date.as_mut()
160    }
161
162    /// A shared reference to the toolchains associated with the release.
163    pub fn toolchains(&self) -> &Vec<toolchain::Toolchain> {
164        &self.toolchains
165    }
166
167    /// An exclusive reference to the toolchains associated with the release.
168    pub fn toolchains_mut(&mut self) -> &mut Vec<toolchain::Toolchain> {
169        &mut self.toolchains
170    }
171
172    /// Iterator over the toolchains associated with the release.
173    pub fn toolchains_iter(&self) -> impl Iterator<Item = &toolchain::Toolchain> {
174        self.toolchains.iter()
175    }
176
177    /// A shared reference to the (added) context of this release.
178    pub fn context(&self) -> &C {
179        &self.context
180    }
181
182    /// An exclusive reference to the (added) context of this release.
183    pub fn context_mut(&mut self) -> &mut C {
184        &mut self.context
185    }
186}
187
188/// A combination of a channel and the version number.
189///
190/// For stable and beta releases, we have a three component MAJOR.MINOR.PATCH
191/// version number. For nightly releases, we have a release date.
192#[derive(Clone, Debug, Eq, PartialEq)]
193pub enum ReleaseVersion {
194    /// A stable channel release version
195    Stable(Stable),
196    /// A beta channel release version
197    Beta(Beta),
198    /// A nightly channel release version
199    Nightly(Nightly),
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use crate::toolchain::Toolchain;
206    use rust_toolchain::RustVersion;
207    use std::collections::HashSet;
208
209    // Create a fake toolchain model
210    fn fake_tc(stable: Stable, date: Option<rust_toolchain::Date>) -> Toolchain {
211        Toolchain::new(
212            rust_toolchain::Channel::Stable(stable),
213            date,
214            rust_toolchain::Target::host(),
215            HashSet::new(),
216            HashSet::new(),
217        )
218    }
219
220    #[yare::parameterized(
221        some = { Some(rust_toolchain::Date::new(2024, 1, 1)) },
222        none = { None },
223    )]
224    fn can_instantiate_deux(date: Option<rust_toolchain::Date>) {
225        let stable = Stable {
226            version: RustVersion::new(1, 82, 0),
227        };
228        let version = ReleaseVersion::Stable(stable.clone());
229        let release = RustRelease::new(version, date.clone(), vec![fake_tc(stable, date)]);
230
231        let target_date = release.toolchains_iter().next().unwrap().date();
232
233        assert_eq!(release.release_date(), target_date);
234    }
235
236    #[test]
237    fn version() {
238        let stable = Stable::new(1, 82, 0);
239        let release = RustRelease::new(stable.clone(), None, vec![fake_tc(stable.clone(), None)]);
240
241        assert_eq!(release.version(), &stable);
242    }
243
244    #[test]
245    fn version_mut() {
246        let stable = Stable::new(1, 82, 0);
247        let mut release =
248            RustRelease::new(stable.clone(), None, vec![fake_tc(stable.clone(), None)]);
249
250        assert_eq!(release.version(), &stable);
251        let replacement = Stable::new(9, 9, 9);
252        *release.version_mut() = replacement.clone();
253
254        assert_eq!(release.version(), &replacement);
255    }
256
257    #[test]
258    fn release_date() {
259        let stable = Stable::new(1, 82, 0);
260        let date = rust_toolchain::Date::new(2026, 12, 12);
261        let release = RustRelease::new(
262            stable.clone(),
263            Some(date.clone()),
264            vec![fake_tc(stable.clone(), Some(date.clone()))],
265        );
266
267        assert_eq!(release.release_date().unwrap(), &date);
268    }
269
270    #[test]
271    fn release_date_mut() {
272        let stable = Stable::new(1, 82, 0);
273        let date = rust_toolchain::Date::new(2026, 12, 12);
274        let mut release = RustRelease::new(
275            stable.clone(),
276            Some(date.clone()),
277            vec![fake_tc(stable.clone(), Some(date.clone()))],
278        );
279
280        assert_eq!(release.release_date().unwrap(), &date);
281        let replacement = rust_toolchain::Date::new(2026, 05, 22);
282        release.release_date_mut().replace(&mut replacement.clone());
283
284        assert_eq!(release.release_date().unwrap(), &date);
285    }
286
287    #[test]
288    fn toolchains() {
289        let stable1 = Stable::new(1, 82, 0);
290        let stable2 = Stable::new(1, 83, 0);
291        let date = rust_toolchain::Date::new(2026, 12, 12);
292        // doesn't really make sense to put different versions in the toolchains vec, but for this test
293        // it is enough, and theoretically it would be possible.
294        let toolchains = vec![
295            fake_tc(stable1.clone(), Some(date.clone())),
296            fake_tc(stable2.clone(), None),
297        ];
298
299        let release = RustRelease::new(stable1.clone(), None, toolchains.clone());
300
301        assert_eq!(release.toolchains(), &toolchains);
302    }
303
304    #[test]
305    fn toolchains_mut() {
306        let stable1 = Stable::new(1, 82, 0);
307        let stable2 = Stable::new(1, 83, 0);
308        let date = rust_toolchain::Date::new(2026, 12, 12);
309        // doesn't really make sense to put different versions in the toolchains vec, but for this test
310        // it is enough, and theoretically it would be possible.
311        let toolchains = vec![
312            fake_tc(stable1.clone(), Some(date.clone())),
313            fake_tc(stable2.clone(), None),
314        ];
315        let mut release = RustRelease::new(stable1.clone(), None, toolchains.clone());
316
317        assert_eq!(release.toolchains(), &toolchains);
318
319        let stable3 = Stable::new(9, 9, 9);
320        let extension = fake_tc(stable3, None);
321        release.toolchains_mut().push(extension.clone());
322
323        let expected = [toolchains, vec![extension]].concat();
324        assert_eq!(release.toolchains(), &expected);
325    }
326
327    #[test]
328    fn context() {
329        #[derive(Copy, Clone, Debug, Eq, PartialEq)]
330        enum Checksum {
331            Crc32(u32),
332        }
333
334        struct MyContext {
335            checksum: Checksum,
336        }
337
338        let stable = Stable::new(1, 82, 0);
339        let date = rust_toolchain::Date::new(2026, 12, 12);
340        let release = RustRelease::new_with_context(
341            stable.clone(),
342            None,
343            vec![fake_tc(stable.clone(), Some(date.clone()))],
344            MyContext {
345                checksum: Checksum::Crc32(0x00000000),
346            },
347        );
348
349        assert_eq!(release.context().checksum, Checksum::Crc32(0x00000000));
350    }
351
352    #[test]
353    fn context_mut() {
354        #[derive(Copy, Clone, Debug, Eq, PartialEq)]
355        enum Checksum {
356            Crc32(u32),
357        }
358
359        struct MyContext {
360            checksum: Checksum,
361        }
362
363        let stable = Stable::new(1, 82, 0);
364        let date = rust_toolchain::Date::new(2026, 12, 12);
365        let mut release = RustRelease::new_with_context(
366            stable.clone(),
367            None,
368            vec![fake_tc(stable.clone(), Some(date.clone()))],
369            MyContext {
370                checksum: Checksum::Crc32(0x00000000),
371            },
372        );
373
374        assert_eq!(release.context().checksum, Checksum::Crc32(0x00000000));
375
376        let replacement = MyContext {
377            checksum: Checksum::Crc32(0xFFFFFFFF),
378        };
379        *release.context_mut() = replacement;
380
381        assert_eq!(release.context.checksum, Checksum::Crc32(0xFFFFFFFF));
382    }
383
384    #[test]
385    fn pattern_match() {
386        let stable = Stable::new(1, 82, 0);
387        let release = RustRelease::new(stable.clone(), None, vec![fake_tc(stable.clone(), None)]);
388
389        let RustRelease {
390            version,
391            toolchains,
392            context,
393            ..
394        } = release;
395
396        assert_eq!(version, stable);
397        assert_eq!(toolchains.len(), 1);
398        assert_eq!(context, ());
399    }
400}