ib_update/github/
async.rs1use super::update_config_builder as builder;
2use crate::github::{Error, Release, UpdateConfig, UpdateConfigBuilder, UpdateInfo};
3use crate::http::AsyncHttp;
4
5pub struct AsyncUpdateChecker {
25 inner: UpdateConfig,
26}
27
28impl AsyncUpdateChecker {
29 pub fn new(config: UpdateConfig) -> Self {
31 Self { inner: config }
32 }
33
34 pub async fn fetch_latest(&self) -> Result<Release, Error> {
36 let res = AsyncHttp::get(
37 self.inner.client_builder(),
38 self.inner.releases_latest_path(),
39 )
40 .await?;
41
42 let status = res.status();
43 let body = res.bytes().await?;
44 Release::parse(status, body)
45 }
46
47 pub async fn fetch(&self, n: usize) -> Result<Vec<Release>, Error> {
51 debug_assert!(n <= 100);
52 let res = AsyncHttp::get(
53 self.inner.client_builder(),
54 self.inner.releases_path(n as u8),
55 )
56 .await?;
57
58 let status = res.status();
59 let body = res.bytes().await?;
60 Release::parse_array(status, body)
61 }
62
63 pub async fn fetch_latest_prerelease(&self) -> Result<Release, Error> {
65 let releases = self.fetch(1).await?;
66 releases.into_iter().next().ok_or(Error::NO_RELEASE)
67 }
68
69 pub async fn check(&self) -> Result<UpdateInfo, Error> {
74 let release = self.fetch_latest().await?;
77 Ok(UpdateInfo::new(
78 self.inner.current_version.as_deref(),
79 release,
80 ))
81 }
82
83 pub async fn check_prerelease(&self, prerelease: bool) -> Result<UpdateInfo, Error> {
87 let release = if prerelease {
88 self.fetch_latest_prerelease().await
89 } else {
90 self.fetch_latest().await
91 }?;
92 Ok(UpdateInfo::new(
93 self.inner.current_version.as_deref(),
94 release,
95 ))
96 }
97}
98
99impl<S: builder::State> UpdateConfigBuilder<S> {
100 pub fn build_async(self) -> AsyncUpdateChecker
121 where
122 S: builder::IsComplete,
123 {
124 let config = self.build();
125 AsyncUpdateChecker::new(config)
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use crate::github::tests::{TEST_OWNER, TEST_REPO};
133
134 #[test]
135 fn builder_and_check() {
136 futures::executor::block_on(async {
137 let info = UpdateConfig::builder()
138 .owner(TEST_OWNER)
139 .repo(TEST_REPO)
140 .current_version("0.1.0")
141 .build_async()
142 .check()
143 .await
144 .unwrap();
145 dbg!(&info);
146 assert!(info.has_update());
147 assert!(!info.latest().assets.is_empty());
148 });
149 }
150
151 #[test]
152 fn fetch_latest() {
153 futures::executor::block_on(async {
154 let release = UpdateConfig::builder()
155 .owner(TEST_OWNER)
156 .repo(TEST_REPO)
157 .build_async()
158 .fetch_latest()
159 .await
160 .unwrap();
161
162 assert!(
163 release.tag.starts_with('v'),
164 "tag should start with 'v', got: {}",
165 release.tag
166 );
167 assert!(release.name.is_some(), "release name should be Some");
168 assert!(!release.name.as_ref().unwrap().is_empty());
169 assert!(!release.assets.is_empty(), "release should have assets");
170
171 for asset in &release.assets {
172 assert!(!asset.name.is_empty());
173 assert!(
174 asset.download_url.starts_with("https://"),
175 "download URL should be HTTPS: {}",
176 asset.download_url
177 );
178 }
179
180 assert!(
181 release.html_url.contains("Chaoses-Ib/IbEverythingExt"),
182 "html_url should contain the repo path"
183 );
184 assert!(release.body.is_some(), "release body should be Some");
185 assert!(!release.body.as_ref().unwrap().is_empty());
186 });
187 }
188
189 #[test]
190 fn fetch() {
191 futures::executor::block_on(async {
192 let releases = UpdateConfig::builder()
193 .owner(TEST_OWNER)
194 .repo(TEST_REPO)
195 .build_async()
196 .fetch(5)
197 .await
198 .unwrap();
199
200 assert!(!releases.is_empty(), "should have at least one release");
201
202 for release in &releases {
203 assert!(!release.tag.is_empty());
204 assert!(release.html_url.contains("Chaoses-Ib/IbEverythingExt"));
205 }
206 });
207 }
208
209 #[test]
210 fn fetch_latest_prerelease() {
211 futures::executor::block_on(async {
212 let release = UpdateConfig::builder()
213 .owner(TEST_OWNER)
214 .repo(TEST_REPO)
215 .build_async()
216 .fetch_latest_prerelease()
217 .await
218 .unwrap();
219
220 assert!(!release.tag.is_empty());
221 assert!(release.html_url.contains("Chaoses-Ib/IbEverythingExt"));
222 });
223 }
224
225 #[test]
226 fn current_version() {
227 futures::executor::block_on(async {
228 let latest = UpdateConfig::builder()
229 .owner(TEST_OWNER)
230 .repo(TEST_REPO)
231 .build_async()
232 .fetch_latest()
233 .await
234 .unwrap();
235
236 let info = UpdateConfig::builder()
237 .owner(TEST_OWNER)
238 .repo(TEST_REPO)
239 .current_version(&latest.tag)
240 .build_async()
241 .check()
242 .await
243 .unwrap();
244
245 assert!(
246 !info.has_update(),
247 "checking against the latest tag ({}) should not need an update",
248 latest.tag
249 );
250 });
251 }
252}