rust_releases_rust_dist/
lib.rs

1#![deny(missing_docs)]
2#![deny(clippy::all)]
3#![deny(unsafe_code)]
4#![allow(clippy::upper_case_acronyms)]
5//! Please, see the [`rust-releases`] for additional documentation on how this crate can be used.
6//!
7//! [`rust-releases`]: https://docs.rs/rust-releases
8
9#[cfg(test)]
10#[macro_use]
11extern crate rust_releases_io;
12
13use regex::{Captures, Regex};
14use rust_releases_core::{semver, Channel, FetchResources, Release, ReleaseIndex, Source};
15use rust_releases_io::Document;
16use std::collections::BTreeSet;
17use std::iter::FromIterator;
18
19pub(crate) mod errors;
20pub(crate) mod fetch;
21
22pub use crate::errors::{RustDistError, RustDistResult};
23
24/// A [`Source`] which obtains its input data from the Rust distribution bucket on AWS S3.
25///
26/// [`Source`]: rust_releases_core::Source
27pub struct RustDist {
28    source: Document,
29}
30
31impl RustDist {
32    #[cfg(test)]
33    pub(crate) fn from_document(source: Document) -> Self {
34        Self { source }
35    }
36}
37
38lazy_static::lazy_static! {
39    static ref MATCHER: Regex =
40        Regex::new(r"(?m)^dist/rustc-(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?:\-(alpha|beta|nightly)(\.\d+))?").unwrap();
41}
42
43impl Source for RustDist {
44    type Error = RustDistError;
45
46    fn build_index(&self) -> Result<ReleaseIndex, Self::Error> {
47        let buffer = self.source.buffer();
48        let content = std::str::from_utf8(buffer).map_err(RustDistError::UnrecognizedText)?;
49
50        let releases = MATCHER
51            .captures_iter(content)
52            .map(parse_release)
53            .collect::<RustDistResult<BTreeSet<Release>>>()?;
54
55        Ok(ReleaseIndex::from_iter(releases))
56    }
57}
58
59fn parse_release(capture: Captures) -> RustDistResult<Release> {
60    const MAJOR: &str = "major";
61    const MINOR: &str = "minor";
62    const PATCH: &str = "patch";
63
64    let major = capture[MAJOR].parse::<u64>().map_err(|_| {
65        RustDistError::UnableToParseVersionNumberComponent(&MAJOR, capture[MAJOR].to_string())
66    })?;
67    let minor = capture[MINOR].parse::<u64>().map_err(|_| {
68        RustDistError::UnableToParseVersionNumberComponent(&MINOR, capture[MINOR].to_string())
69    })?;
70    let patch = capture[PATCH].parse::<u64>().map_err(|_| {
71        RustDistError::UnableToParseVersionNumberComponent(&PATCH, capture[PATCH].to_string())
72    })?;
73
74    Ok(Release::new_stable(semver::Version::new(
75        major, minor, patch,
76    )))
77}
78
79impl FetchResources for RustDist {
80    type Error = RustDistError;
81
82    fn fetch_channel(channel: Channel) -> Result<Self, Self::Error> {
83        if let Channel::Stable = channel {
84            let source = fetch::fetch()?;
85            Ok(Self { source })
86        } else {
87            Err(RustDistError::ChannelNotAvailable(channel))
88        }
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use crate::RustDist;
95    use rust_releases_core::{semver, Release, ReleaseIndex};
96    use rust_releases_io::Document;
97    use std::fs;
98
99    #[test]
100    fn source_rust_dist() {
101        let path = [
102            env!("CARGO_MANIFEST_DIR"),
103            "/../../resources/rust_dist/dist_static-rust-lang-org.txt",
104        ]
105        .join("");
106
107        let buffer = fs::read(path).unwrap();
108        let document = Document::new(buffer);
109
110        let strategy = RustDist::from_document(document);
111        let index = ReleaseIndex::from_source(strategy).unwrap();
112
113        // 74 releases including minor releases from 1.0.0 to 1.53.0
114        let releases = index.releases();
115
116        assert_eq!(releases.len(), 74);
117        assert_eq!(
118            releases[0],
119            Release::new_stable(semver::Version::new(1, 53, 0))
120        );
121        assert_eq!(
122            releases[releases.len() - 1],
123            Release::new_stable(semver::Version::new(1, 0, 0))
124        );
125    }
126}