Skip to main content

rust_releases_rust_changelog/
source.rs

1use crate::changelog::{self, ReleaseDate};
2use crate::error::RustChangelogError;
3use rust_releases_core::StableReleases;
4use rust_releases_io::{AsyncRustReleasesClient, ResourceFile, RustReleasesClient};
5use std::borrow::Cow;
6
7pub const RUST_CHANGELOG_URL: &str =
8    "https://raw.githubusercontent.com/rust-lang/rust/master/RELEASES.md";
9
10pub const RUST_CHANGELOG_RESOURCE_NAME: &str = "RELEASES.md";
11
12#[cfg(any(feature = "ureq", feature = "reqwest"))]
13const DEFAULT_CACHE_FOLDER: &str = "source_rust_changelog";
14
15#[cfg(any(feature = "ureq", feature = "reqwest"))]
16const DEFAULT_CACHE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(86_400);
17
18#[derive(Clone, Debug)]
19pub struct RustChangelog<C> {
20    client: C,
21    url: Cow<'static, str>,
22    resource_name: Cow<'static, str>,
23    today: ReleaseDate,
24}
25
26impl<C> RustChangelog<C> {
27    pub fn new(client: C) -> Self {
28        Self {
29            client,
30            url: Cow::Borrowed(RUST_CHANGELOG_URL),
31            resource_name: Cow::Borrowed(RUST_CHANGELOG_RESOURCE_NAME),
32            today: ReleaseDate::today(),
33        }
34    }
35
36    pub fn with_url(mut self, url: impl Into<Cow<'static, str>>) -> Self {
37        self.url = url.into();
38        self
39    }
40
41    pub fn with_resource_name(mut self, resource_name: impl Into<Cow<'static, str>>) -> Self {
42        self.resource_name = resource_name.into();
43        self
44    }
45
46    pub fn with_today(mut self, today: ReleaseDate) -> Self {
47        self.today = today;
48        self
49    }
50
51    pub fn client(&self) -> &C {
52        &self.client
53    }
54
55    pub fn url(&self) -> &str {
56        &self.url
57    }
58
59    pub fn resource_name(&self) -> &str {
60        &self.resource_name
61    }
62
63    pub fn today(&self) -> ReleaseDate {
64        self.today
65    }
66
67    fn collect_changelog<E>(&self, buffer: &[u8]) -> Result<StableReleases, RustChangelogError<E>> {
68        changelog::stable_releases(buffer, &self.today).map_err(|source| {
69            RustChangelogError::Parse {
70                url: self.url.to_string(),
71                source,
72            }
73        })
74    }
75
76    fn fetch_error<E>(&self, source: E) -> RustChangelogError<E> {
77        RustChangelogError::Fetch {
78            url: self.url.to_string(),
79            source,
80        }
81    }
82}
83
84impl<C: RustReleasesClient> RustChangelog<C> {
85    pub fn fetch(&self) -> Result<StableReleases, RustChangelogError<C::Error>> {
86        let retrieved = self
87            .client
88            .fetch(ResourceFile::new(&self.url, &self.resource_name))
89            .map_err(|source| self.fetch_error(source))?;
90
91        self.collect_changelog(retrieved.into_document().buffer())
92    }
93}
94
95impl<C: AsyncRustReleasesClient> RustChangelog<C> {
96    pub async fn fetch_async(&self) -> Result<StableReleases, RustChangelogError<C::Error>> {
97        let retrieved = self
98            .client
99            .fetch(ResourceFile::new(&self.url, &self.resource_name))
100            .await
101            .map_err(|source| self.fetch_error(source))?;
102
103        self.collect_changelog(retrieved.into_document().buffer())
104    }
105}
106
107#[cfg(feature = "ureq")]
108impl RustChangelog<rust_releases_io::HttpCachedClient<rust_releases_io::UreqTransport>> {
109    pub fn new_ureq_cached_client() -> Result<Self, rust_releases_io::BaseCacheDirError> {
110        let cache_folder = rust_releases_io::base_cache_dir()?.join(DEFAULT_CACHE_FOLDER);
111
112        Ok(Self::new(
113            rust_releases_io::HttpCachedClient::new_with_default_client(
114                cache_folder,
115                DEFAULT_CACHE_TIMEOUT,
116            ),
117        ))
118    }
119}
120
121#[cfg(feature = "reqwest")]
122impl RustChangelog<rust_releases_io::HttpCachedClient<rust_releases_io::ReqwestTransport>> {
123    pub fn new_reqwest_cached_client() -> Result<Self, rust_releases_io::BaseCacheDirError> {
124        let cache_folder = rust_releases_io::base_cache_dir()?.join(DEFAULT_CACHE_FOLDER);
125
126        Ok(Self::new(rust_releases_io::HttpCachedClient::new(
127            rust_releases_io::HttpClient::new(rust_releases_io::ReqwestTransport::new()),
128            cache_folder,
129            DEFAULT_CACHE_TIMEOUT,
130        )))
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    fn instance() -> RustChangelog<()> {
139        RustChangelog::new(())
140    }
141
142    #[test]
143    fn default_url() {
144        assert_eq!(
145            instance().url(),
146            "https://raw.githubusercontent.com/rust-lang/rust/master/RELEASES.md"
147        );
148    }
149
150    #[test]
151    fn default_resource_name() {
152        assert_eq!(instance().resource_name(), "RELEASES.md");
153    }
154
155    #[test]
156    fn configured_url_and_resource_name() {
157        let source = instance()
158            .with_url("https://example.com/rust/RELEASES.md")
159            .with_resource_name("example-RELEASES.md");
160
161        assert_eq!(source.url(), "https://example.com/rust/RELEASES.md");
162        assert_eq!(source.resource_name(), "example-RELEASES.md");
163    }
164
165    #[test]
166    fn configured_today() {
167        let today = ReleaseDate::parse("2021-09-01").unwrap();
168
169        assert_eq!(instance().with_today(today).today(), today);
170    }
171}