From 188e033ed64626ca90ae221c24e891dc58a5ddd9 Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Fri, 1 Nov 2024 18:07:44 +0800 Subject: [PATCH 1/7] views: Prepare to expose the `yanked` field on the API The `yanked` state is based on the `default_version`. The `default_version` can only be yanked if all other versions are also yanked. A crate should only be yanked if all of its versions are yanked. --- src/controllers/krate/metadata.rs | 1 + src/controllers/krate/publish.rs | 1 + src/controllers/krate/search.rs | 1 + src/controllers/summary.rs | 1 + src/views.rs | 7 +++++++ 5 files changed, 11 insertions(+) diff --git a/src/controllers/krate/metadata.rs b/src/controllers/krate/metadata.rs index 0c10c4197ab..c8637e12608 100644 --- a/src/controllers/krate/metadata.rs +++ b/src/controllers/krate/metadata.rs @@ -126,6 +126,7 @@ pub async fn show(app: AppState, Path(name): Path, req: Parts) -> AppRes let encodable_crate = EncodableCrate::from( krate.clone(), default_version.as_deref(), + None, top_versions.as_ref(), ids, kws.as_deref(), diff --git a/src/controllers/krate/publish.rs b/src/controllers/krate/publish.rs index 71226e231cc..15a265173db 100644 --- a/src/controllers/krate/publish.rs +++ b/src/controllers/krate/publish.rs @@ -519,6 +519,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult AppResult> { EncodableCrate::from_minimal( krate, default_version.as_deref(), + None, Some(&max_version), perfect_match, total, diff --git a/src/controllers/summary.rs b/src/controllers/summary.rs index d92d520f30f..22561b4db53 100644 --- a/src/controllers/summary.rs +++ b/src/controllers/summary.rs @@ -47,6 +47,7 @@ pub async fn summary(state: AppState) -> AppResult> { Ok(EncodableCrate::from_minimal( krate, default_version.as_deref(), + None, Some(&top_versions), false, total, diff --git a/src/views.rs b/src/views.rs index 1e4631ef71b..0820cb61f68 100644 --- a/src/views.rs +++ b/src/views.rs @@ -209,6 +209,8 @@ pub struct EncodableCrate { pub downloads: i64, pub recent_downloads: Option, pub default_version: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub yanked: Option, // NOTE: Used by shields.io, altering `max_version` requires a PR with shields.io pub max_version: String, pub newest_version: String, // Most recently updated version, which may not be max @@ -226,6 +228,7 @@ impl EncodableCrate { pub fn from( krate: Crate, default_version: Option<&str>, + yanked: Option, top_versions: Option<&TopVersions>, versions: Option>, keywords: Option<&[Keyword]>, @@ -296,6 +299,7 @@ impl EncodableCrate { categories: category_ids, badges: [], default_version, + yanked, max_version, newest_version, max_stable_version, @@ -318,6 +322,7 @@ impl EncodableCrate { pub fn from_minimal( krate: Crate, default_version: Option<&str>, + yanked: Option, top_versions: Option<&TopVersions>, exact_match: bool, downloads: i64, @@ -326,6 +331,7 @@ impl EncodableCrate { Self::from( krate, default_version, + yanked, top_versions, None, None, @@ -808,6 +814,7 @@ mod tests { downloads: 0, recent_downloads: None, default_version: None, + yanked: None, max_version: "".to_string(), newest_version: "".to_string(), max_stable_version: None, From 2ca8b54dbd1b4774a7357a8ecd5d91b36ada4522 Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Fri, 1 Nov 2024 18:33:26 +0800 Subject: [PATCH 2/7] controllers/summary: Expose the `yanked` field --- src/controllers/summary.rs | 29 ++++++++++------- src/tests/routes/summary.rs | 62 +++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 12 deletions(-) diff --git a/src/controllers/summary.rs b/src/controllers/summary.rs index 22561b4db53..0881f37a7f8 100644 --- a/src/controllers/summary.rs +++ b/src/controllers/summary.rs @@ -34,7 +34,7 @@ pub async fn summary(state: AppState) -> AppResult> { fn encode_crates( conn: &mut impl Conn, - data: Vec<(Crate, i64, Option, Option)>, + data: Vec, ) -> AppResult> { let krates = data.iter().map(|(c, ..)| c).collect::>(); let versions: Vec = krates.versions().load(conn)?; @@ -43,17 +43,19 @@ pub async fn summary(state: AppState) -> AppResult> { .into_iter() .map(TopVersions::from_versions) .zip(data) - .map(|(top_versions, (krate, total, recent, default_version))| { - Ok(EncodableCrate::from_minimal( - krate, - default_version.as_deref(), - None, - Some(&top_versions), - false, - total, - recent, - )) - }) + .map( + |(top_versions, (krate, total, recent, default_version, yanked))| { + Ok(EncodableCrate::from_minimal( + krate, + default_version.as_deref(), + yanked, + Some(&top_versions), + false, + total, + recent, + )) + }, + ) .collect() } @@ -62,6 +64,7 @@ pub async fn summary(state: AppState) -> AppResult> { crate_downloads::downloads, recent_crate_downloads::downloads.nullable(), versions::num.nullable(), + versions::yanked.nullable(), ); let new_crates = crates::table @@ -137,3 +140,5 @@ pub async fn summary(state: AppState) -> AppResult> { }) .await } + +type Record = (Crate, i64, Option, Option, Option); diff --git a/src/tests/routes/summary.rs b/src/tests/routes/summary.rs index d7ed2cb0846..a62a5df5af0 100644 --- a/src/tests/routes/summary.rs +++ b/src/tests/routes/summary.rs @@ -100,6 +100,7 @@ async fn summary_new_crates() { json.most_downloaded[0].default_version, Some("0.2.0".into()) ); + assert_eq!(json.most_downloaded[0].yanked, Some(false)); assert_eq!(json.most_downloaded[0].downloads, 5000); assert_eq!(json.most_downloaded[0].recent_downloads, Some(50)); assert_eq!( @@ -110,6 +111,7 @@ async fn summary_new_crates() { json.most_recently_downloaded[0].default_version, Some("0.2.0".into()) ); + assert_eq!(json.most_recently_downloaded[0].yanked, Some(false)); assert_eq!(json.most_recently_downloaded[0].recent_downloads, Some(50)); assert_eq!(json.popular_keywords[0].keyword, "popular"); assert_eq!(json.popular_categories[0].category, "Category 1"); @@ -117,16 +119,19 @@ async fn summary_new_crates() { assert_eq!(json.just_updated[0].name, "just_updated_patch"); assert_eq!(json.just_updated[0].default_version, Some("0.2.0".into())); + assert_eq!(json.just_updated[0].yanked, Some(false)); assert_eq!(json.just_updated[0].max_version, "0.2.0"); assert_eq!(json.just_updated[0].newest_version, "0.1.1"); assert_eq!(json.just_updated[1].name, "just_updated"); assert_eq!(json.just_updated[1].default_version, Some("0.1.2".into())); + assert_eq!(json.just_updated[1].yanked, Some(false)); assert_eq!(json.just_updated[1].max_version, "0.1.2"); assert_eq!(json.just_updated[1].newest_version, "0.1.2"); assert_eq!(json.new_crates[0].name, "with_downloads"); assert_eq!(json.new_crates[0].default_version, Some("0.3.0".into())); + assert_eq!(json.new_crates[0].yanked, Some(false)); assert_eq!(json.new_crates.len(), 5); } @@ -172,6 +177,7 @@ async fn excluded_crate_id() { json.most_downloaded[0].default_version, Some("0.1.0".into()) ); + assert_eq!(json.most_downloaded[0].yanked, Some(false)); assert_eq!(json.most_downloaded[0].downloads, 20); assert_eq!(json.most_recently_downloaded.len(), 1); @@ -180,5 +186,61 @@ async fn excluded_crate_id() { json.most_recently_downloaded[0].default_version, Some("0.1.0".into()) ); + assert_eq!(json.most_recently_downloaded[0].yanked, Some(false)); + assert_eq!(json.most_recently_downloaded[0].recent_downloads, Some(10)); +} + +#[tokio::test(flavor = "multi_thread")] +async fn all_yanked() { + let (app, anon, user) = TestApp::init() + .with_config(|config| { + config.excluded_crate_names = vec![ + "most_recent_downloads".into(), + // make sure no error occurs with a crate name that doesn't exist and that the name + // matches are exact, not substrings + "downloads".into(), + ]; + }) + .with_user(); + + let mut conn = app.db_conn(); + let user = user.as_model(); + + CrateBuilder::new("some_downloads", user.id) + .version(VersionBuilder::new("0.1.0").yanked(true)) + .version(VersionBuilder::new("0.2.0").yanked(true)) + .description("description") + .keyword("popular") + .category("cat1") + .downloads(20) + .recent_downloads(10) + .expect_build(&mut conn); + + CrateBuilder::new("most_recent_downloads", user.id) + .version(VersionBuilder::new("0.2.0")) + .keyword("popular") + .category("cat1") + .downloads(5000) + .recent_downloads(50) + .expect_build(&mut conn); + + let json: SummaryResponse = anon.get("/api/v1/summary").await.good(); + + assert_eq!(json.most_downloaded.len(), 1); + assert_eq!(json.most_downloaded[0].name, "some_downloads"); + assert_eq!( + json.most_downloaded[0].default_version, + Some("0.2.0".into()) + ); + assert_eq!(json.most_downloaded[0].yanked, Some(true)); + assert_eq!(json.most_downloaded[0].downloads, 20); + + assert_eq!(json.most_recently_downloaded.len(), 1); + assert_eq!(json.most_recently_downloaded[0].name, "some_downloads"); + assert_eq!( + json.most_recently_downloaded[0].default_version, + Some("0.2.0".into()) + ); + assert_eq!(json.most_recently_downloaded[0].yanked, Some(true)); assert_eq!(json.most_recently_downloaded[0].recent_downloads, Some(10)); } From e403960ec04c1f5d37ce828bba800dcb170d4abc Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Fri, 1 Nov 2024 18:42:23 +0800 Subject: [PATCH 3/7] controllers/krate/metadata: Expose the `yanked` field --- src/controllers/krate/metadata.rs | 33 ++--- src/tests/routes/crates/read.rs | 28 +++++ ...tests__routes__crates__read__new_name.snap | 3 +- ...io__tests__routes__crates__read__show.snap | 3 +- ...routes__crates__read__show_all_yanked.snap | 118 ++++++++++++++++++ ...s__routes__crates__read__show_minimal.snap | 3 +- 6 files changed, 171 insertions(+), 17 deletions(-) create mode 100644 src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show_all_yanked.snap diff --git a/src/controllers/krate/metadata.rs b/src/controllers/krate/metadata.rs index c8637e12608..4f96b0207d8 100644 --- a/src/controllers/krate/metadata.rs +++ b/src/controllers/krate/metadata.rs @@ -45,19 +45,24 @@ pub async fn show(app: AppState, Path(name): Path, req: Parts) -> AppRes .transpose()? .unwrap_or_default(); - let (krate, downloads, default_version): (Crate, i64, Option) = - Crate::by_name(&name) - .inner_join(crate_downloads::table) - .left_join(default_versions::table) - .left_join(versions::table.on(default_versions::version_id.eq(versions::id))) - .select(( - Crate::as_select(), - crate_downloads::downloads, - versions::num.nullable(), - )) - .first(conn) - .optional()? - .ok_or_else(|| crate_not_found(&name))?; + let (krate, downloads, default_version, yanked): ( + Crate, + i64, + Option, + Option, + ) = Crate::by_name(&name) + .inner_join(crate_downloads::table) + .left_join(default_versions::table) + .left_join(versions::table.on(default_versions::version_id.eq(versions::id))) + .select(( + Crate::as_select(), + crate_downloads::downloads, + versions::num.nullable(), + versions::yanked.nullable(), + )) + .first(conn) + .optional()? + .ok_or_else(|| crate_not_found(&name))?; let versions_publishers_and_audit_actions = if include.versions { let mut versions_and_publishers: Vec<(Version, Option)> = krate @@ -126,7 +131,7 @@ pub async fn show(app: AppState, Path(name): Path, req: Parts) -> AppRes let encodable_crate = EncodableCrate::from( krate.clone(), default_version.as_deref(), - None, + yanked, top_versions.as_ref(), ids, kws.as_deref(), diff --git a/src/tests/routes/crates/read.rs b/src/tests/routes/crates/read.rs index dcf669f2e70..074fa46ed19 100644 --- a/src/tests/routes/crates/read.rs +++ b/src/tests/routes/crates/read.rs @@ -73,6 +73,34 @@ async fn show_minimal() { }); } +#[tokio::test(flavor = "multi_thread")] +async fn show_all_yanked() { + let (app, anon, user) = TestApp::init().with_user(); + let mut conn = app.db_conn(); + let user = user.as_model(); + + CrateBuilder::new("foo_show", user.id) + .description("description") + .documentation("https://example.com") + .homepage("http://example.com") + .version(VersionBuilder::new("1.0.0").yanked(true)) + .version(VersionBuilder::new("0.5.0").yanked(true)) + .keyword("kw1") + .downloads(20) + .recent_downloads(10) + .expect_build(&mut conn); + + let response = anon.get::<()>("/api/v1/crates/foo_show").await; + assert_eq!(response.status(), StatusCode::OK); + assert_json_snapshot!(response.json(), { + ".crate.created_at" => "[datetime]", + ".crate.updated_at" => "[datetime]", + ".keywords[].created_at" => "[datetime]", + ".versions[].created_at" => "[datetime]", + ".versions[].updated_at" => "[datetime]", + }); +} + #[tokio::test(flavor = "multi_thread")] async fn test_missing() { let (_, anon) = TestApp::init().empty(); diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__new_name.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__new_name.snap index 1c4bf7042f4..026be79f9c0 100644 --- a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__new_name.snap +++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__new_name.snap @@ -31,7 +31,8 @@ expression: response.json() "recent_downloads": null, "repository": null, "updated_at": "[datetime]", - "versions": null + "versions": null, + "yanked": false }, "keywords": null, "versions": null diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show.snap index d7d2f764f92..59bd91b56ff 100644 --- a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show.snap +++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show.snap @@ -37,7 +37,8 @@ expression: response.json() 1, 3, 2 - ] + ], + "yanked": false }, "keywords": [ { diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show_all_yanked.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show_all_yanked.snap new file mode 100644 index 00000000000..b0fe373088c --- /dev/null +++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show_all_yanked.snap @@ -0,0 +1,118 @@ +--- +source: src/tests/routes/crates/read.rs +expression: response.json() +--- +{ + "categories": [], + "crate": { + "badges": [], + "categories": [], + "created_at": "[datetime]", + "default_version": "1.0.0", + "description": "description", + "documentation": "https://example.com", + "downloads": 20, + "exact_match": false, + "homepage": "http://example.com", + "id": "foo_show", + "keywords": [ + "kw1" + ], + "links": { + "owner_team": "/api/v1/crates/foo_show/owner_team", + "owner_user": "/api/v1/crates/foo_show/owner_user", + "owners": "/api/v1/crates/foo_show/owners", + "reverse_dependencies": "/api/v1/crates/foo_show/reverse_dependencies", + "version_downloads": "/api/v1/crates/foo_show/downloads", + "versions": null + }, + "max_stable_version": null, + "max_version": "0.0.0", + "name": "foo_show", + "newest_version": "0.0.0", + "recent_downloads": 10, + "repository": null, + "updated_at": "[datetime]", + "versions": [ + 1, + 2 + ], + "yanked": true + }, + "keywords": [ + { + "crates_cnt": 1, + "created_at": "[datetime]", + "id": "kw1", + "keyword": "kw1" + } + ], + "versions": [ + { + "audit_actions": [], + "bin_names": null, + "checksum": " ", + "crate": "foo_show", + "crate_size": 0, + "created_at": "[datetime]", + "dl_path": "/api/v1/crates/foo_show/1.0.0/download", + "downloads": 0, + "features": {}, + "has_lib": null, + "id": 1, + "lib_links": null, + "license": null, + "links": { + "authors": "/api/v1/crates/foo_show/1.0.0/authors", + "dependencies": "/api/v1/crates/foo_show/1.0.0/dependencies", + "version_downloads": "/api/v1/crates/foo_show/1.0.0/downloads" + }, + "num": "1.0.0", + "published_by": { + "avatar": null, + "id": 1, + "login": "foo", + "name": null, + "url": "https://github.com/foo" + }, + "readme_path": "/api/v1/crates/foo_show/1.0.0/readme", + "rust_version": null, + "updated_at": "[datetime]", + "yank_message": null, + "yanked": true + }, + { + "audit_actions": [], + "bin_names": null, + "checksum": " ", + "crate": "foo_show", + "crate_size": 0, + "created_at": "[datetime]", + "dl_path": "/api/v1/crates/foo_show/0.5.0/download", + "downloads": 0, + "features": {}, + "has_lib": null, + "id": 2, + "lib_links": null, + "license": null, + "links": { + "authors": "/api/v1/crates/foo_show/0.5.0/authors", + "dependencies": "/api/v1/crates/foo_show/0.5.0/dependencies", + "version_downloads": "/api/v1/crates/foo_show/0.5.0/downloads" + }, + "num": "0.5.0", + "published_by": { + "avatar": null, + "id": 1, + "login": "foo", + "name": null, + "url": "https://github.com/foo" + }, + "readme_path": "/api/v1/crates/foo_show/0.5.0/readme", + "rust_version": null, + "updated_at": "[datetime]", + "yank_message": null, + "yanked": true + } + ] +} diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show_minimal.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show_minimal.snap index 1a46596f22b..a040d208028 100644 --- a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show_minimal.snap +++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show_minimal.snap @@ -31,7 +31,8 @@ expression: response.json() "recent_downloads": null, "repository": null, "updated_at": "[datetime]", - "versions": null + "versions": null, + "yanked": false }, "keywords": null, "versions": null From 033e3a02693bf9dad6e5deaf404d50046b0298c5 Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Fri, 1 Nov 2024 19:17:19 +0800 Subject: [PATCH 4/7] controllers/krate/search: Expose the `yanked` field --- src/controllers/krate/search.rs | 22 ++++++++++++++++++---- src/tests/routes/crates/list.rs | 23 +++++++++++++++++++---- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/controllers/krate/search.rs b/src/controllers/krate/search.rs index 579556a4b48..576b8920c44 100644 --- a/src/controllers/krate/search.rs +++ b/src/controllers/krate/search.rs @@ -88,6 +88,7 @@ pub async fn search(app: AppState, req: Parts) -> AppResult> { recent_crate_downloads::downloads.nullable(), 0_f32.into_sql::(), versions::num.nullable(), + versions::yanked.nullable(), ); let mut seek: Option = None; @@ -117,6 +118,7 @@ pub async fn search(app: AppState, req: Parts) -> AppResult> { recent_crate_downloads::downloads.nullable(), rank.clone(), versions::num.nullable(), + versions::yanked.nullable(), )); seek = Some(Seek::Relevance); query = query.then_order_by(rank.desc()) @@ -128,6 +130,7 @@ pub async fn search(app: AppState, req: Parts) -> AppResult> { recent_crate_downloads::downloads.nullable(), 0_f32.into_sql::(), versions::num.nullable(), + versions::yanked.nullable(), )); seek = Some(Seek::Query); } @@ -231,11 +234,14 @@ pub async fn search(app: AppState, req: Parts) -> AppResult> { let crates = versions .zip(data) .map( - |(max_version, (krate, perfect_match, total, recent, _, default_version))| { + |( + max_version, + (krate, perfect_match, total, recent, _, default_version, yanked), + )| { EncodableCrate::from_minimal( krate, default_version.as_deref(), - None, + yanked, Some(&max_version), perfect_match, total, @@ -622,7 +628,7 @@ mod seek { downloads, recent_downloads, rank, - _, + .., ) = *record; match *self { @@ -645,7 +651,15 @@ mod seek { } } -type Record = (Crate, bool, i64, Option, f32, Option); +type Record = ( + Crate, + bool, + i64, + Option, + f32, + Option, + Option, +); type QuerySource = LeftJoinQuerySource< LeftJoinQuerySource< diff --git a/src/tests/routes/crates/list.rs b/src/tests/routes/crates/list.rs index f5b3b291df0..180f5964feb 100644 --- a/src/tests/routes/crates/list.rs +++ b/src/tests/routes/crates/list.rs @@ -713,11 +713,18 @@ async fn index_include_yanked() { assert_eq!(json.crates[1].name, "newest_yanked"); assert_eq!(json.crates[2].name, "oldest_yanked"); assert_eq!(json.crates[3].name, "unyanked"); + assert_eq!( default_versions_iter(&json.crates) - .flatten() + .flat_map(|s| s.as_deref()) + .zip(yanked_iter(&json.crates).flatten().cloned()) .collect::>(), - ["2.0.0", "1.0.0", "2.0.0", "2.0.0",] + [ + ("2.0.0", true), + ("1.0.0", false), + ("2.0.0", false), + ("2.0.0", false), + ] ); } @@ -729,9 +736,10 @@ async fn index_include_yanked() { assert_eq!(json.crates[2].name, "unyanked"); assert_eq!( default_versions_iter(&json.crates) - .flatten() + .flat_map(|s| s.as_deref()) + .zip(yanked_iter(&json.crates).flatten().cloned()) .collect::>(), - ["1.0.0", "2.0.0", "2.0.0",] + [("1.0.0", false), ("2.0.0", false), ("2.0.0", false),] ); } } @@ -751,6 +759,7 @@ async fn yanked_versions_are_not_considered_for_max_version() { for json in search_both(&anon, "q=foo").await { assert_eq!(json.meta.total, 1); assert_eq!(json.crates[0].default_version, Some("1.0.0".into())); + assert_eq!(json.crates[0].yanked, Some(false)); assert_eq!(json.crates[0].max_version, "1.0.0"); } } @@ -773,6 +782,7 @@ async fn max_stable_version() { for json in search_both(&anon, "q=foo").await { assert_eq!(json.meta.total, 1); assert_eq!(json.crates[0].default_version, Some("1.0.0".into())); + assert_eq!(json.crates[0].yanked, Some(false)); assert_eq!(json.crates[0].max_stable_version, Some("1.0.0".to_string())); } } @@ -1149,6 +1159,7 @@ async fn page_with_seek( url = Some(new_url.to_owned()); assert_ne!(resp.meta.total, 0); assert!(default_versions_iter(&resp.crates).all(Option::is_some)); + assert!(yanked_iter(&resp.crates).all(Option::is_some)); } else { assert_that!(resp.crates, empty()); assert_eq!(resp.meta.total, 0); @@ -1163,3 +1174,7 @@ fn default_versions_iter( ) -> impl Iterator> { crates.iter().map(|c| &c.default_version) } + +fn yanked_iter(crates: &[crate::tests::EncodableCrate]) -> impl Iterator> { + crates.iter().map(|c| &c.yanked) +} From c55729b12c9fffb9d3e1146fa5157754edf6123d Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Fri, 1 Nov 2024 19:22:19 +0800 Subject: [PATCH 5/7] controllers/krate/publish: Expose the `yanked` field --- src/controllers/krate/publish.rs | 2 +- .../crates_io__tests__krate__publish__basics__new_krate.snap | 3 ++- ...tes_io__tests__krate__publish__basics__new_krate_twice.snap | 3 ++- ...io__tests__krate__publish__basics__new_krate_twice_alt.snap | 3 ++- ...tests__krate__publish__basics__new_krate_weird_version.snap | 3 ++- ...o__tests__krate__publish__basics__new_krate_with_token.snap | 3 ++- ...metadata__version_with_build_metadata@build_metadata_1.snap | 3 ++- ...metadata__version_with_build_metadata@build_metadata_2.snap | 3 ++- ...metadata__version_with_build_metadata@build_metadata_3.snap | 3 ++- ...io__tests__krate__publish__categories__good_categories.snap | 3 ++- ...s_io__tests__krate__publish__dependencies__dep_limit-2.snap | 3 ++- ...tes_io__tests__krate__publish__keywords__good_keywords.snap | 3 ++- ...__tests__krate__publish__links__crate_with_links_field.snap | 3 ++- ...es_io__tests__krate__publish__manifest__boolean_readme.snap | 3 ++- ...io__tests__krate__publish__manifest__lib_and_bin_crate.snap | 3 ++- ...tarball_between_default_axum_limit_and_max_upload_size.snap | 3 ++- ...s__krate__publish__readme__new_krate_with_empty_readme.snap | 3 ++- ...__tests__krate__publish__readme__new_krate_with_readme.snap | 3 ++- ...ublish__readme__new_krate_with_readme_and_plus_version.snap | 3 ++- 19 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/controllers/krate/publish.rs b/src/controllers/krate/publish.rs index 15a265173db..6430423b369 100644 --- a/src/controllers/krate/publish.rs +++ b/src/controllers/krate/publish.rs @@ -519,7 +519,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult Date: Fri, 1 Nov 2024 19:31:03 +0800 Subject: [PATCH 6/7] views: Always serialize the `yanked` field --- src/views.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/views.rs b/src/views.rs index 0820cb61f68..8d56ce486eb 100644 --- a/src/views.rs +++ b/src/views.rs @@ -209,7 +209,6 @@ pub struct EncodableCrate { pub downloads: i64, pub recent_downloads: Option, pub default_version: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub yanked: Option, // NOTE: Used by shields.io, altering `max_version` requires a PR with shields.io pub max_version: String, From c3ee544da417358d62a507a69b282d4b679edf83 Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Sun, 3 Nov 2024 22:42:56 +0800 Subject: [PATCH 7/7] views: Always serialize the `yanked` field as a boolean --- src/tests/routes/crates/list.rs | 11 +++++------ src/tests/routes/summary.rs | 18 +++++++++--------- src/views.rs | 5 +++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/tests/routes/crates/list.rs b/src/tests/routes/crates/list.rs index 180f5964feb..14495793697 100644 --- a/src/tests/routes/crates/list.rs +++ b/src/tests/routes/crates/list.rs @@ -717,7 +717,7 @@ async fn index_include_yanked() { assert_eq!( default_versions_iter(&json.crates) .flat_map(|s| s.as_deref()) - .zip(yanked_iter(&json.crates).flatten().cloned()) + .zip(yanked_iter(&json.crates).cloned()) .collect::>(), [ ("2.0.0", true), @@ -737,7 +737,7 @@ async fn index_include_yanked() { assert_eq!( default_versions_iter(&json.crates) .flat_map(|s| s.as_deref()) - .zip(yanked_iter(&json.crates).flatten().cloned()) + .zip(yanked_iter(&json.crates).cloned()) .collect::>(), [("1.0.0", false), ("2.0.0", false), ("2.0.0", false),] ); @@ -759,7 +759,7 @@ async fn yanked_versions_are_not_considered_for_max_version() { for json in search_both(&anon, "q=foo").await { assert_eq!(json.meta.total, 1); assert_eq!(json.crates[0].default_version, Some("1.0.0".into())); - assert_eq!(json.crates[0].yanked, Some(false)); + assert!(!json.crates[0].yanked); assert_eq!(json.crates[0].max_version, "1.0.0"); } } @@ -782,7 +782,7 @@ async fn max_stable_version() { for json in search_both(&anon, "q=foo").await { assert_eq!(json.meta.total, 1); assert_eq!(json.crates[0].default_version, Some("1.0.0".into())); - assert_eq!(json.crates[0].yanked, Some(false)); + assert!(!json.crates[0].yanked); assert_eq!(json.crates[0].max_stable_version, Some("1.0.0".to_string())); } } @@ -1159,7 +1159,6 @@ async fn page_with_seek( url = Some(new_url.to_owned()); assert_ne!(resp.meta.total, 0); assert!(default_versions_iter(&resp.crates).all(Option::is_some)); - assert!(yanked_iter(&resp.crates).all(Option::is_some)); } else { assert_that!(resp.crates, empty()); assert_eq!(resp.meta.total, 0); @@ -1175,6 +1174,6 @@ fn default_versions_iter( crates.iter().map(|c| &c.default_version) } -fn yanked_iter(crates: &[crate::tests::EncodableCrate]) -> impl Iterator> { +fn yanked_iter(crates: &[crate::tests::EncodableCrate]) -> impl Iterator { crates.iter().map(|c| &c.yanked) } diff --git a/src/tests/routes/summary.rs b/src/tests/routes/summary.rs index a62a5df5af0..a49ca7bb42b 100644 --- a/src/tests/routes/summary.rs +++ b/src/tests/routes/summary.rs @@ -100,7 +100,7 @@ async fn summary_new_crates() { json.most_downloaded[0].default_version, Some("0.2.0".into()) ); - assert_eq!(json.most_downloaded[0].yanked, Some(false)); + assert!(!json.most_downloaded[0].yanked); assert_eq!(json.most_downloaded[0].downloads, 5000); assert_eq!(json.most_downloaded[0].recent_downloads, Some(50)); assert_eq!( @@ -111,7 +111,7 @@ async fn summary_new_crates() { json.most_recently_downloaded[0].default_version, Some("0.2.0".into()) ); - assert_eq!(json.most_recently_downloaded[0].yanked, Some(false)); + assert!(!json.most_recently_downloaded[0].yanked); assert_eq!(json.most_recently_downloaded[0].recent_downloads, Some(50)); assert_eq!(json.popular_keywords[0].keyword, "popular"); assert_eq!(json.popular_categories[0].category, "Category 1"); @@ -119,19 +119,19 @@ async fn summary_new_crates() { assert_eq!(json.just_updated[0].name, "just_updated_patch"); assert_eq!(json.just_updated[0].default_version, Some("0.2.0".into())); - assert_eq!(json.just_updated[0].yanked, Some(false)); + assert!(!json.just_updated[0].yanked); assert_eq!(json.just_updated[0].max_version, "0.2.0"); assert_eq!(json.just_updated[0].newest_version, "0.1.1"); assert_eq!(json.just_updated[1].name, "just_updated"); assert_eq!(json.just_updated[1].default_version, Some("0.1.2".into())); - assert_eq!(json.just_updated[1].yanked, Some(false)); + assert!(!json.just_updated[1].yanked); assert_eq!(json.just_updated[1].max_version, "0.1.2"); assert_eq!(json.just_updated[1].newest_version, "0.1.2"); assert_eq!(json.new_crates[0].name, "with_downloads"); assert_eq!(json.new_crates[0].default_version, Some("0.3.0".into())); - assert_eq!(json.new_crates[0].yanked, Some(false)); + assert!(!json.new_crates[0].yanked); assert_eq!(json.new_crates.len(), 5); } @@ -177,7 +177,7 @@ async fn excluded_crate_id() { json.most_downloaded[0].default_version, Some("0.1.0".into()) ); - assert_eq!(json.most_downloaded[0].yanked, Some(false)); + assert!(!json.most_downloaded[0].yanked); assert_eq!(json.most_downloaded[0].downloads, 20); assert_eq!(json.most_recently_downloaded.len(), 1); @@ -186,7 +186,7 @@ async fn excluded_crate_id() { json.most_recently_downloaded[0].default_version, Some("0.1.0".into()) ); - assert_eq!(json.most_recently_downloaded[0].yanked, Some(false)); + assert!(!json.most_recently_downloaded[0].yanked); assert_eq!(json.most_recently_downloaded[0].recent_downloads, Some(10)); } @@ -232,7 +232,7 @@ async fn all_yanked() { json.most_downloaded[0].default_version, Some("0.2.0".into()) ); - assert_eq!(json.most_downloaded[0].yanked, Some(true)); + assert!(json.most_downloaded[0].yanked); assert_eq!(json.most_downloaded[0].downloads, 20); assert_eq!(json.most_recently_downloaded.len(), 1); @@ -241,6 +241,6 @@ async fn all_yanked() { json.most_recently_downloaded[0].default_version, Some("0.2.0".into()) ); - assert_eq!(json.most_recently_downloaded[0].yanked, Some(true)); + assert!(json.most_recently_downloaded[0].yanked); assert_eq!(json.most_recently_downloaded[0].recent_downloads, Some(10)); } diff --git a/src/views.rs b/src/views.rs index 8d56ce486eb..dfc90c72eab 100644 --- a/src/views.rs +++ b/src/views.rs @@ -209,7 +209,7 @@ pub struct EncodableCrate { pub downloads: i64, pub recent_downloads: Option, pub default_version: Option, - pub yanked: Option, + pub yanked: bool, // NOTE: Used by shields.io, altering `max_version` requires a PR with shields.io pub max_version: String, pub newest_version: String, // Most recently updated version, which may not be max @@ -261,6 +261,7 @@ impl EncodableCrate { let message = format!("Crate `{name}` has no default version"); sentry::capture_message(&message, sentry::Level::Info); } + let yanked = yanked.unwrap_or_default(); let max_version = top_versions .and_then(|v| v.highest.as_ref()) @@ -813,7 +814,7 @@ mod tests { downloads: 0, recent_downloads: None, default_version: None, - yanked: None, + yanked: false, max_version: "".to_string(), newest_version: "".to_string(), max_stable_version: None,