rust_releases_rust_changelog/
source.rs1use 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}