Skip to content

Commit 2ba1b48

Browse files
committed
registry-api: gracefully handle releases not found
1 parent 8d6520a commit 2ba1b48

6 files changed

Lines changed: 136 additions & 12 deletions

File tree

crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ impl RustwideBuilder {
786786
.runtime
787787
.block_on(self.registry_api.get_release_data(name, version))
788788
{
789-
Ok(data) => Some(data),
789+
Ok(data) => data,
790790
Err(err) => {
791791
error!(%name, %version, ?err, "could not fetch releases-data");
792792
None

crates/bin/docs_rs_import_release/src/import.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
rustdoc::{download_static_files, find_static_paths, find_successful_build_targets},
55
rustdoc_status::fetch_rustdoc_status,
66
};
7-
use anyhow::{Result, bail};
7+
use anyhow::{Result, anyhow, bail};
88
use docs_rs_cargo_metadata::CargoMetadata;
99
use docs_rs_database::releases::{
1010
finish_build, finish_release, initialize_build, initialize_crate, initialize_release,
@@ -117,7 +117,10 @@ async fn import_test_release_inner(
117117
(files_list, source_size)
118118
};
119119

120-
let registry_data = registry_api.get_release_data(name, version).await?;
120+
let registry_data = registry_api
121+
.get_release_data(name, version)
122+
.await?
123+
.ok_or_else(|| anyhow!("release not found in registry"))?;
121124

122125
let rustdoc_dir = {
123126
info!("download & extract rustdoc archive...");

crates/lib/docs_rs_registry_api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ tracing = { workspace = true }
2222
url = { workspace = true }
2323

2424
[dev-dependencies]
25+
docs_rs_types = { path = "../docs_rs_types", features = ["testing"] }
2526
mime = { workspace = true }
2627
mockito = { workspace = true }
2728
test-case = { workspace = true }

crates/lib/docs_rs_registry_api/src/api.rs

Lines changed: 128 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ use crate::{
33
error::{Error, Result},
44
models::{ApiErrors, CrateData, CrateOwner, OwnerKind, ReleaseData, Search, SearchResponse},
55
};
6-
use anyhow::{Context, anyhow};
76
use chrono::{DateTime, Utc};
87
use docs_rs_types::{KrateName, Version};
98
use docs_rs_utils::{APP_USER_AGENT, retry_async};
10-
use reqwest::header::{ACCEPT, HeaderValue, USER_AGENT};
9+
use reqwest::{
10+
StatusCode,
11+
header::{ACCEPT, HeaderValue, USER_AGENT},
12+
};
1113
use serde::{Deserialize, de::DeserializeOwned};
1214
use tracing::instrument;
1315
use url::Url;
@@ -111,7 +113,7 @@ impl RegistryApi {
111113
&self,
112114
name: &KrateName,
113115
version: &Version,
114-
) -> Result<ReleaseData> {
116+
) -> Result<Option<ReleaseData>> {
115117
let url = {
116118
let mut url = self.api_base.clone();
117119
url.path_segments_mut()
@@ -136,19 +138,25 @@ impl RegistryApi {
136138
downloads: i32,
137139
}
138140

139-
let response: Response = self.request(&url).await?;
141+
let response: Response = match self.request(&url).await {
142+
Ok(response) => response,
143+
Err(err) if err.status() == Some(StatusCode::NOT_FOUND) => return Ok(None),
144+
Err(err) => return Err(err),
145+
};
140146

141-
let version = response
147+
let Some(version) = response
142148
.versions
143149
.into_iter()
144150
.find(|data| data.num == *version)
145-
.with_context(|| anyhow!("Could not find version in response"))?;
151+
else {
152+
return Ok(None);
153+
};
146154

147-
Ok(ReleaseData {
155+
Ok(Some(ReleaseData {
148156
release_time: version.created_at,
149157
yanked: version.yanked,
150158
downloads: version.downloads,
151-
})
159+
}))
152160
}
153161

154162
/// Fetch owners from the registry's API
@@ -222,6 +230,8 @@ impl RegistryApi {
222230
mod tests {
223231
use super::*;
224232
use crate::models::{ApiError, SearchCrate, SearchMeta};
233+
use anyhow::anyhow;
234+
use docs_rs_types::testing::{KRATE, V1, V2};
225235
use reqwest::{StatusCode, header::CONTENT_TYPE};
226236
use serde::Serialize;
227237
use test_case::test_case;
@@ -241,6 +251,25 @@ mod tests {
241251
api.search("q=foo").await
242252
}
243253

254+
async fn test_get_release(
255+
status: StatusCode,
256+
body: impl Serialize,
257+
version: &Version,
258+
) -> Result<Option<ReleaseData>> {
259+
let mut crates_io_api = mockito::Server::new_async().await;
260+
261+
let _m = crates_io_api
262+
.mock("GET", "/api/v1/crates/krate/versions")
263+
.with_status(status.as_u16().into())
264+
.with_header(CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
265+
.with_body(serde_json::to_vec(&body).unwrap())
266+
.create_async()
267+
.await;
268+
269+
let api = RegistryApi::new(crates_io_api.url().parse().unwrap(), 0)?;
270+
api.get_release_data(&KRATE, version).await
271+
}
272+
244273
#[test]
245274
fn test_error_without_status() {
246275
for err in [
@@ -397,4 +426,95 @@ mod tests {
397426

398427
Ok(())
399428
}
429+
430+
#[tokio::test]
431+
async fn test_get_release_ok() -> Result<()> {
432+
let release_data = test_get_release(
433+
StatusCode::OK,
434+
serde_json::json!({
435+
"versions": [
436+
{
437+
"num": V1.to_string(),
438+
"created_at": "2024-01-01T00:00:00Z",
439+
"yanked": false,
440+
"downloads": 42
441+
},
442+
{
443+
"num": V2.to_string(),
444+
"created_at": "2024-01-02T00:00:00Z",
445+
"yanked": true,
446+
"downloads": 100
447+
}
448+
]
449+
}),
450+
&V1,
451+
)
452+
.await?
453+
.expect("found version");
454+
455+
assert_eq!(
456+
release_data,
457+
ReleaseData {
458+
release_time: DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z")
459+
.unwrap()
460+
.with_timezone(&Utc),
461+
yanked: false,
462+
downloads: 42,
463+
}
464+
);
465+
466+
Ok(())
467+
}
468+
469+
#[tokio::test]
470+
async fn test_get_release_not_found_empty_result() -> Result<()> {
471+
assert!(
472+
test_get_release(
473+
StatusCode::OK,
474+
serde_json::json!({
475+
"versions": []
476+
}),
477+
&V1,
478+
)
479+
.await?
480+
.is_none()
481+
);
482+
483+
Ok(())
484+
}
485+
486+
#[tokio::test]
487+
async fn test_get_release_not_found_other_version() -> Result<()> {
488+
assert!(
489+
test_get_release(
490+
StatusCode::OK,
491+
serde_json::json!({
492+
"versions": [
493+
{
494+
"num": V1.to_string(),
495+
"created_at": "2024-01-01T00:00:00Z",
496+
"yanked": false,
497+
"downloads": 42
498+
}
499+
]
500+
}),
501+
&V2,
502+
)
503+
.await?
504+
.is_none()
505+
);
506+
507+
Ok(())
508+
}
509+
510+
#[tokio::test]
511+
async fn test_get_release_not_found_404() -> Result<()> {
512+
assert!(
513+
test_get_release(StatusCode::NOT_FOUND, "", &V1)
514+
.await?
515+
.is_none()
516+
);
517+
518+
Ok(())
519+
}
400520
}

crates/lib/docs_rs_registry_api/src/error.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ mod tests {
6161
let status = StatusCode::INTERNAL_SERVER_ERROR;
6262

6363
assert!(Error::CrateIoApiError(status, ApiErrors::default()).status() == Some(status));
64-
6564
assert!(Error::CrateIoError(status, "".into()).status() == Some(status));
6665
}
6766

crates/lib/docs_rs_registry_api/src/models.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub struct CrateData {
88
}
99

1010
#[derive(Debug)]
11+
#[cfg_attr(test, derive(PartialEq))]
1112
pub struct ReleaseData {
1213
pub release_time: DateTime<Utc>,
1314
pub yanked: bool,

0 commit comments

Comments
 (0)