From b1e96f890c4d5e6dccad8a7f10fa9e1f157d191e Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Thu, 24 Oct 2024 01:46:21 +0800 Subject: [PATCH 1/7] views: Prepare to expose the `default_version` field on the API --- src/controllers/krate/metadata.rs | 1 + src/controllers/krate/publish.rs | 10 +++++++++- src/controllers/krate/search.rs | 1 + src/controllers/summary.rs | 1 + src/views.rs | 9 +++++++++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/controllers/krate/metadata.rs b/src/controllers/krate/metadata.rs index 7d4fbe8dd4b..016affa7e32 100644 --- a/src/controllers/krate/metadata.rs +++ b/src/controllers/krate/metadata.rs @@ -120,6 +120,7 @@ pub async fn show(app: AppState, Path(name): Path, req: Parts) -> AppRes let encodable_crate = EncodableCrate::from( krate.clone(), + None, top_versions.as_ref(), ids, kws.as_deref(), diff --git a/src/controllers/krate/publish.rs b/src/controllers/krate/publish.rs index e864108f81d..6a2e8e5b614 100644 --- a/src/controllers/krate/publish.rs +++ b/src/controllers/krate/publish.rs @@ -505,7 +505,15 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult AppResult> { .map(|(max_version, (krate, perfect_match, total, recent, _))| { EncodableCrate::from_minimal( krate, + None, Some(&max_version), Some(vec![]), perfect_match, diff --git a/src/controllers/summary.rs b/src/controllers/summary.rs index ff08c366a25..c403aaef806 100644 --- a/src/controllers/summary.rs +++ b/src/controllers/summary.rs @@ -37,6 +37,7 @@ pub async fn summary(state: AppState) -> AppResult> { .map(|(top_versions, (krate, total, recent))| { Ok(EncodableCrate::from_minimal( krate, + None, Some(&top_versions), None, false, diff --git a/src/views.rs b/src/views.rs index 9b7f5728361..a0cf1f02ad3 100644 --- a/src/views.rs +++ b/src/views.rs @@ -208,6 +208,8 @@ pub struct EncodableCrate { // NOTE: Used by shields.io, altering `downloads` requires a PR with shields.io pub downloads: i64, pub recent_downloads: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub default_version: 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 @@ -224,6 +226,7 @@ impl EncodableCrate { #[allow(clippy::too_many_arguments)] pub fn from( krate: Crate, + default_version: Option<&str>, top_versions: Option<&TopVersions>, versions: Option>, keywords: Option<&[Keyword]>, @@ -254,6 +257,8 @@ impl EncodableCrate { let documentation = remove_blocked_urls(documentation); let repository = remove_blocked_urls(repository); + let default_version = default_version.map(ToString::to_string); + let max_version = top_versions .and_then(|v| v.highest.as_ref()) .map(|v| v.to_string()) @@ -289,6 +294,7 @@ impl EncodableCrate { keywords: keyword_ids, categories: category_ids, badges, + default_version, max_version, newest_version, max_stable_version, @@ -310,6 +316,7 @@ impl EncodableCrate { pub fn from_minimal( krate: Crate, + default_version: Option<&str>, top_versions: Option<&TopVersions>, badges: Option>, exact_match: bool, @@ -318,6 +325,7 @@ impl EncodableCrate { ) -> Self { Self::from( krate, + default_version, top_versions, None, None, @@ -800,6 +808,7 @@ mod tests { .unwrap(), downloads: 0, recent_downloads: None, + default_version: None, max_version: "".to_string(), newest_version: "".to_string(), max_stable_version: None, From eb5e7598116d51f6e2a0b4abbeb6bbdedf0f06a1 Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Thu, 24 Oct 2024 02:13:19 +0800 Subject: [PATCH 2/7] controllers/summary: Expose the `default_version` field --- src/controllers/summary.rs | 19 +++++++++++++++---- src/tests/routes/summary.rs | 25 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/controllers/summary.rs b/src/controllers/summary.rs index c403aaef806..1b89de04443 100644 --- a/src/controllers/summary.rs +++ b/src/controllers/summary.rs @@ -1,6 +1,8 @@ use crate::app::AppState; use crate::models::{Category, Crate, CrateVersions, Keyword, TopVersions, Version}; -use crate::schema::{crate_downloads, crates, keywords, metadata, recent_crate_downloads}; +use crate::schema::{ + crate_downloads, crates, default_versions, keywords, metadata, recent_crate_downloads, versions, +}; use crate::tasks::spawn_blocking; use crate::util::diesel::Conn; use crate::util::errors::AppResult; @@ -25,7 +27,7 @@ pub async fn summary(state: AppState) -> AppResult> { fn encode_crates( conn: &mut impl Conn, - data: Vec<(Crate, i64, Option)>, + data: Vec<(Crate, i64, Option, Option)>, ) -> AppResult> { let krates = data.iter().map(|(c, ..)| c).collect::>(); let versions: Vec = krates.versions().load(conn)?; @@ -34,10 +36,10 @@ pub async fn summary(state: AppState) -> AppResult> { .into_iter() .map(TopVersions::from_versions) .zip(data) - .map(|(top_versions, (krate, total, recent))| { + .map(|(top_versions, (krate, total, recent, default_version))| { Ok(EncodableCrate::from_minimal( krate, - None, + default_version.as_deref(), Some(&top_versions), None, false, @@ -52,11 +54,14 @@ pub async fn summary(state: AppState) -> AppResult> { Crate::as_select(), crate_downloads::downloads, recent_crate_downloads::downloads.nullable(), + versions::num.nullable(), ); let new_crates = crates::table .inner_join(crate_downloads::table) .left_join(recent_crate_downloads::table) + .left_join(default_versions::table) + .left_join(versions::table.on(default_versions::version_id.eq(versions::id))) .order(crates::created_at.desc()) .select(selection) .limit(10) @@ -64,6 +69,8 @@ pub async fn summary(state: AppState) -> AppResult> { let just_updated = crates::table .inner_join(crate_downloads::table) .left_join(recent_crate_downloads::table) + .left_join(default_versions::table) + .left_join(versions::table.on(default_versions::version_id.eq(versions::id))) .filter(crates::updated_at.ne(crates::created_at)) .order(crates::updated_at.desc()) .select(selection) @@ -73,6 +80,8 @@ pub async fn summary(state: AppState) -> AppResult> { let mut most_downloaded_query = crates::table .inner_join(crate_downloads::table) .left_join(recent_crate_downloads::table) + .left_join(default_versions::table) + .left_join(versions::table.on(default_versions::version_id.eq(versions::id))) .into_boxed(); if !config.excluded_crate_names.is_empty() { most_downloaded_query = @@ -87,6 +96,8 @@ pub async fn summary(state: AppState) -> AppResult> { let mut most_recently_downloaded_query = crates::table .inner_join(crate_downloads::table) .inner_join(recent_crate_downloads::table) + .left_join(default_versions::table) + .left_join(versions::table.on(default_versions::version_id.eq(versions::id))) .into_boxed(); if !config.excluded_crate_names.is_empty() { most_recently_downloaded_query = most_recently_downloaded_query diff --git a/src/tests/routes/summary.rs b/src/tests/routes/summary.rs index a0a59ae4e62..d7ed2cb0846 100644 --- a/src/tests/routes/summary.rs +++ b/src/tests/routes/summary.rs @@ -51,6 +51,7 @@ async fn summary_new_crates() { CrateBuilder::new("most_recent_downloads", user.id) .version(VersionBuilder::new("0.2.0")) + .version(VersionBuilder::new("0.2.1").yanked(true)) .keyword("popular") .category("cat1") .downloads(5000) @@ -59,6 +60,7 @@ async fn summary_new_crates() { CrateBuilder::new("just_updated", user.id) .version(VersionBuilder::new("0.1.0")) + .version(VersionBuilder::new("0.1.1").yanked(true)) .version(VersionBuilder::new("0.1.2")) // update 'just_updated' krate. Others won't appear because updated_at == created_at. .updated_at(now_) @@ -67,6 +69,7 @@ async fn summary_new_crates() { CrateBuilder::new("just_updated_patch", user.id) .version(VersionBuilder::new("0.1.0")) .version(VersionBuilder::new("0.2.0")) + .version(VersionBuilder::new("0.2.1").yanked(true)) // Add a patch version be newer than the other versions, including the higher one. .version(VersionBuilder::new("0.1.1").created_at(now_plus_two)) .updated_at(now_plus_two) @@ -74,6 +77,7 @@ async fn summary_new_crates() { CrateBuilder::new("with_downloads", user.id) .version(VersionBuilder::new("0.3.0")) + .version(VersionBuilder::new("0.3.1").yanked(true)) .keyword("popular") .downloads(1000) .expect_build(conn); @@ -92,25 +96,37 @@ async fn summary_new_crates() { assert_eq!(json.num_crates, 5); assert_eq!(json.num_downloads, 6000); assert_eq!(json.most_downloaded[0].name, "most_recent_downloads"); + assert_eq!( + json.most_downloaded[0].default_version, + Some("0.2.0".into()) + ); assert_eq!(json.most_downloaded[0].downloads, 5000); assert_eq!(json.most_downloaded[0].recent_downloads, Some(50)); assert_eq!( json.most_recently_downloaded[0].name, "most_recent_downloads" ); + assert_eq!( + json.most_recently_downloaded[0].default_version, + Some("0.2.0".into()) + ); 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"); assert_eq!(json.just_updated.len(), 2); 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].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].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.len(), 5); } @@ -132,6 +148,7 @@ async fn excluded_crate_id() { CrateBuilder::new("some_downloads", user.id) .version(VersionBuilder::new("0.1.0")) + .version(VersionBuilder::new("0.2.0").yanked(true)) .description("description") .keyword("popular") .category("cat1") @@ -151,9 +168,17 @@ async fn excluded_crate_id() { 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.1.0".into()) + ); 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.1.0".into()) + ); assert_eq!(json.most_recently_downloaded[0].recent_downloads, Some(10)); } From 594c7cbeffdd2c2a2dd1b14332acee8e27f598d8 Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Thu, 24 Oct 2024 02:28:30 +0800 Subject: [PATCH 3/7] controllers/krate/metadata: Expose the `default_version` field --- src/controllers/krate/metadata.rs | 21 ++++++++++++------- ...tests__routes__crates__read__new_name.snap | 1 + ...io__tests__routes__crates__read__show.snap | 1 + ...s__routes__crates__read__show_minimal.snap | 1 + 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/controllers/krate/metadata.rs b/src/controllers/krate/metadata.rs index 016affa7e32..1028ada0a7f 100644 --- a/src/controllers/krate/metadata.rs +++ b/src/controllers/krate/metadata.rs @@ -45,12 +45,19 @@ pub async fn show(app: AppState, Path(name): Path, req: Parts) -> AppRes .transpose()? .unwrap_or_default(); - let (krate, downloads): (Crate, i64) = Crate::by_name(&name) - .inner_join(crate_downloads::table) - .select((Crate::as_select(), crate_downloads::downloads)) - .first(conn) - .optional()? - .ok_or_else(|| crate_not_found(&name))?; + 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 versions_publishers_and_audit_actions = if include.versions { let mut versions_and_publishers: Vec<(Version, Option)> = krate @@ -120,7 +127,7 @@ pub async fn show(app: AppState, Path(name): Path, req: Parts) -> AppRes let encodable_crate = EncodableCrate::from( krate.clone(), - None, + default_version.as_deref(), top_versions.as_ref(), ids, kws.as_deref(), 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 84339deb761..8707fb5d777 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 @@ -8,6 +8,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "0.99.0", "description": null, "documentation": null, "downloads": 0, 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 50eeaaf38c6..d7d2f764f92 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 @@ -8,6 +8,7 @@ expression: response.json() "badges": [], "categories": [], "created_at": "[datetime]", + "default_version": "1.0.0", "description": "description", "documentation": "https://example.com", "downloads": 20, 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 e8ebdc43251..3812e18f9a8 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 @@ -8,6 +8,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0", "description": "description", "documentation": "https://example.com", "downloads": 20, From caf043c36de68ae2777ca14615538bafe8c6af56 Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Thu, 24 Oct 2024 03:30:21 +0800 Subject: [PATCH 4/7] controllers/krate/search: Expose the `default_version` field --- src/controllers/krate/search.rs | 43 ++++++++++++++++++++++----------- src/tests/routes/crates/list.rs | 29 ++++++++++++++++++++++ 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/controllers/krate/search.rs b/src/controllers/krate/search.rs index 08eda1aab90..1b777957a43 100644 --- a/src/controllers/krate/search.rs +++ b/src/controllers/krate/search.rs @@ -87,6 +87,7 @@ pub async fn search(app: AppState, req: Parts) -> AppResult> { crate_downloads::downloads, recent_crate_downloads::downloads.nullable(), 0_f32.into_sql::(), + versions::num.nullable(), ); let mut seek: Option = None; @@ -94,6 +95,8 @@ pub async fn search(app: AppState, req: Parts) -> AppResult> { .make_query(&req, conn)? .inner_join(crate_downloads::table) .left_join(recent_crate_downloads::table) + .left_join(default_versions::table) + .left_join(versions::table.on(default_versions::version_id.eq(versions::id))) .select(selection); if let Some(q_string) = &filter_params.q_string { @@ -113,6 +116,7 @@ pub async fn search(app: AppState, req: Parts) -> AppResult> { crate_downloads::downloads, recent_crate_downloads::downloads.nullable(), rank.clone(), + versions::num.nullable(), )); seek = Some(Seek::Relevance); query = query.then_order_by(rank.desc()) @@ -123,6 +127,7 @@ pub async fn search(app: AppState, req: Parts) -> AppResult> { crate_downloads::downloads, recent_crate_downloads::downloads.nullable(), 0_f32.into_sql::(), + versions::num.nullable(), )); seek = Some(Seek::Query); } @@ -225,17 +230,19 @@ pub async fn search(app: AppState, req: Parts) -> AppResult> { let crates = versions .zip(data) - .map(|(max_version, (krate, perfect_match, total, recent, _))| { - EncodableCrate::from_minimal( - krate, - None, - Some(&max_version), - Some(vec![]), - perfect_match, - total, - Some(recent.unwrap_or(0)), - ) - }) + .map( + |(max_version, (krate, perfect_match, total, recent, _, default_version))| { + EncodableCrate::from_minimal( + krate, + default_version.as_deref(), + Some(&max_version), + Some(vec![]), + perfect_match, + total, + Some(recent.unwrap_or(0)), + ) + }, + ) .collect::>(); Ok(Json(json!({ @@ -615,6 +622,7 @@ mod seek { downloads, recent_downloads, rank, + _, ) = *record; match *self { @@ -637,11 +645,18 @@ mod seek { } } -type Record = (Crate, bool, i64, Option, f32); +type Record = (Crate, bool, i64, Option, f32, Option); type QuerySource = LeftJoinQuerySource< - InnerJoinQuerySource, - recent_crate_downloads::table, + LeftJoinQuerySource< + LeftJoinQuerySource< + InnerJoinQuerySource, + recent_crate_downloads::table, + >, + default_versions::table, + >, + versions::table, + diesel::dsl::Eq, >; type BoxedCondition<'a> = Box< diff --git a/src/tests/routes/crates/list.rs b/src/tests/routes/crates/list.rs index 43528853da1..f5b3b291df0 100644 --- a/src/tests/routes/crates/list.rs +++ b/src/tests/routes/crates/list.rs @@ -713,6 +713,12 @@ 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() + .collect::>(), + ["2.0.0", "1.0.0", "2.0.0", "2.0.0",] + ); } // Do not include fully yanked (all versions were yanked) crates @@ -721,6 +727,12 @@ async fn index_include_yanked() { assert_eq!(json.crates[0].name, "newest_yanked"); assert_eq!(json.crates[1].name, "oldest_yanked"); assert_eq!(json.crates[2].name, "unyanked"); + assert_eq!( + default_versions_iter(&json.crates) + .flatten() + .collect::>(), + ["1.0.0", "2.0.0", "2.0.0",] + ); } } @@ -738,6 +750,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].max_version, "1.0.0"); } } @@ -759,6 +772,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].max_stable_version, Some("1.0.0".to_string())); } } @@ -929,6 +943,9 @@ async fn pagination_links_included_if_applicable() { .iter() .all(|w| *w == 3)); assert_eq!(page4.meta.total, 0); + for p in [page1, page2, page3, page4] { + assert!(default_versions_iter(&p.crates).all(Option::is_some)); + } } #[tokio::test(flavor = "multi_thread")] @@ -960,6 +977,7 @@ async fn seek_based_pagination() { assert_that!(resp.crates, len(eq(1))); url = Some(new_url); assert_eq!(resp.meta.total, 3); + assert!(default_versions_iter(&resp.crates).all(Option::is_some)); } else { assert_that!(resp.crates, empty()); assert_eq!(resp.meta.total, 0); @@ -993,11 +1011,13 @@ async fn test_pages_work_even_with_seek_based_pagination() { let first = anon.search("per_page=1").await; assert!(first.meta.next_page.unwrap().contains("seek=")); assert_eq!(first.meta.total, 3); + assert!(default_versions_iter(&first.crates).all(Option::is_some)); // Calling with page=2 will revert to offset-based pagination let second = anon.search("page=2&per_page=1").await; assert!(second.meta.next_page.unwrap().contains("page=3")); assert_eq!(second.meta.total, 3); + assert!(default_versions_iter(&second.crates).all(Option::is_some)); } #[tokio::test(flavor = "multi_thread")] @@ -1096,6 +1116,8 @@ async fn search_both(anon: &U, query: &str) -> [crate::tests:: .as_deref() .unwrap_or("seek=") .contains("seek=")); + assert!(default_versions_iter(&offset.crates).all(Option::is_some)); + assert!(default_versions_iter(&seek.crates).all(Option::is_some)); [offset, seek] } @@ -1126,6 +1148,7 @@ async fn page_with_seek( assert_that!(resp.crates, len(eq(1))); url = Some(new_url.to_owned()); assert_ne!(resp.meta.total, 0); + assert!(default_versions_iter(&resp.crates).all(Option::is_some)); } else { assert_that!(resp.crates, empty()); assert_eq!(resp.meta.total, 0); @@ -1134,3 +1157,9 @@ async fn page_with_seek( } (results, calls) } + +fn default_versions_iter( + crates: &[crate::tests::EncodableCrate], +) -> impl Iterator> { + crates.iter().map(|c| &c.default_version) +} From 4c7785fd71e7de3d4d349baf11026e89c80747a5 Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Thu, 24 Oct 2024 03:54:07 +0800 Subject: [PATCH 5/7] controllers/krate/publish: Expose the `default_version` field --- src/controllers/krate/publish.rs | 5 ++- src/tests/krate/publish/basics.rs | 31 ++++++++++++++ ...ts__krate__publish__basics__new_krate.snap | 1 + ...ate__publish__basics__new_krate_twice.snap | 1 + ...ublish__basics__new_krate_twice_alt-2.snap | 22 ++++++++++ ..._publish__basics__new_krate_twice_alt.snap | 40 +++++++++++++++++++ ...lish__basics__new_krate_weird_version.snap | 1 + ...publish__basics__new_krate_with_token.snap | 1 + ..._with_build_metadata@build_metadata_1.snap | 1 + ..._with_build_metadata@build_metadata_2.snap | 1 + ..._with_build_metadata@build_metadata_3.snap | 1 + ..._publish__categories__good_categories.snap | 1 + ...e__publish__dependencies__dep_limit-2.snap | 1 + ...ate__publish__keywords__good_keywords.snap | 1 + ...ublish__links__crate_with_links_field.snap | 1 + ...te__publish__manifest__boolean_readme.snap | 1 + ..._publish__manifest__lib_and_bin_crate.snap | 1 + ...efault_axum_limit_and_max_upload_size.snap | 1 + ...__readme__new_krate_with_empty_readme.snap | 1 + ...ublish__readme__new_krate_with_readme.snap | 1 + ...ew_krate_with_readme_and_plus_version.snap | 1 + 21 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_twice_alt-2.snap create mode 100644 src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_twice_alt.snap diff --git a/src/controllers/krate/publish.rs b/src/controllers/krate/publish.rs index 6a2e8e5b614..5ddd1648c51 100644 --- a/src/controllers/krate/publish.rs +++ b/src/controllers/krate/publish.rs @@ -392,6 +392,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult AppResult AppResult "[datetime]", + ".crate.updated_at" => "[datetime]", + }); + + let crates = app.crates_from_index_head("foo_twice"); + assert_json_snapshot!(crates); + + assert_snapshot!(app.stored_files().await.join("\n"), @r###" + crates/foo_twice/foo_twice-0.99.0.crate + crates/foo_twice/foo_twice-2.0.0.crate + index/fo/o_/foo_twice + rss/crates.xml + rss/crates/foo_twice.xml + rss/updates.xml + "###); +} + #[tokio::test(flavor = "multi_thread")] async fn new_krate_duplicate_version() { let (app, _, user, token) = TestApp::full().with_token(); diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate.snap index d957a6eaf39..258a759e078 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_twice.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_twice.snap index f6d4987cc03..85c6b2dec86 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_twice.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_twice.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "2.0.0", "description": "2.0.0 description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_twice_alt-2.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_twice_alt-2.snap new file mode 100644 index 00000000000..e45856509db --- /dev/null +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_twice_alt-2.snap @@ -0,0 +1,22 @@ +--- +source: src/tests/krate/publish/basics.rs +expression: crates +--- +[ + { + "name": "foo_twice", + "vers": "2.0.0", + "deps": [], + "cksum": "d6e88a7d30b9e5c3d268ede9a9937b62815e45a06fd2c572d602e0705ab6513d", + "features": {}, + "yanked": false + }, + { + "name": "foo_twice", + "vers": "0.99.0", + "deps": [], + "cksum": "45b0b19cd0280034e07820789d9bb6e4016526eba85c75fc697d49ec99fd2550", + "features": {}, + "yanked": false + } +] diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_twice_alt.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_twice_alt.snap new file mode 100644 index 00000000000..6ad4d1383b7 --- /dev/null +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_twice_alt.snap @@ -0,0 +1,40 @@ +--- +source: src/tests/krate/publish/basics.rs +expression: response.json() +--- +{ + "crate": { + "badges": null, + "categories": null, + "created_at": "[datetime]", + "default_version": "2.0.0", + "description": "description", + "documentation": null, + "downloads": 0, + "exact_match": false, + "homepage": null, + "id": "foo_twice", + "keywords": null, + "links": { + "owner_team": "/api/v1/crates/foo_twice/owner_team", + "owner_user": "/api/v1/crates/foo_twice/owner_user", + "owners": "/api/v1/crates/foo_twice/owners", + "reverse_dependencies": "/api/v1/crates/foo_twice/reverse_dependencies", + "version_downloads": "/api/v1/crates/foo_twice/downloads", + "versions": "/api/v1/crates/foo_twice/versions" + }, + "max_stable_version": "2.0.0", + "max_version": "2.0.0", + "name": "foo_twice", + "newest_version": "0.99.0", + "recent_downloads": null, + "repository": null, + "updated_at": "[datetime]", + "versions": null + }, + "warnings": { + "invalid_badges": [], + "invalid_categories": [], + "other": [] + } +} diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_weird_version.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_weird_version.snap index ae86231a39a..2b264629654 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_weird_version.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_weird_version.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "0.0.0-pre", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_with_token.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_with_token.snap index d957a6eaf39..258a759e078 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_with_token.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__basics__new_krate_with_token.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__build_metadata__version_with_build_metadata@build_metadata_1.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__build_metadata__version_with_build_metadata@build_metadata_1.snap index bc6099907bd..a7b77bc9132 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__build_metadata__version_with_build_metadata@build_metadata_1.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__build_metadata__version_with_build_metadata@build_metadata_1.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0+foo", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__build_metadata__version_with_build_metadata@build_metadata_2.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__build_metadata__version_with_build_metadata@build_metadata_2.snap index 55e368ffc4a..e1f072e1da3 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__build_metadata__version_with_build_metadata@build_metadata_2.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__build_metadata__version_with_build_metadata@build_metadata_2.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0-beta.1", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__build_metadata__version_with_build_metadata@build_metadata_3.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__build_metadata__version_with_build_metadata@build_metadata_3.snap index bc6099907bd..a7b77bc9132 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__build_metadata__version_with_build_metadata@build_metadata_3.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__build_metadata__version_with_build_metadata@build_metadata_3.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0+foo", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__categories__good_categories.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__categories__good_categories.snap index 584e92800a5..c02dd2d47b1 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__categories__good_categories.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__categories__good_categories.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__dependencies__dep_limit-2.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__dependencies__dep_limit-2.snap index 3b96efcfe13..57da71a5e83 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__dependencies__dep_limit-2.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__dependencies__dep_limit-2.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__keywords__good_keywords.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__keywords__good_keywords.snap index f30049bc760..5c27457424f 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__keywords__good_keywords.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__keywords__good_keywords.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__links__crate_with_links_field.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__links__crate_with_links_field.snap index 4fdb51e079f..4fb4f9216f9 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__links__crate_with_links_field.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__links__crate_with_links_field.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0", "description": "foo?!", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__boolean_readme.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__boolean_readme.snap index 6276f5c9547..ef0b92bf8d4 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__boolean_readme.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__boolean_readme.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__lib_and_bin_crate.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__lib_and_bin_crate.snap index 6276f5c9547..ef0b92bf8d4 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__lib_and_bin_crate.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__lib_and_bin_crate.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__max_size__tarball_between_default_axum_limit_and_max_upload_size.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__max_size__tarball_between_default_axum_limit_and_max_upload_size.snap index ef3c98fcfc5..6c68ee608bd 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__max_size__tarball_between_default_axum_limit_and_max_upload_size.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__max_size__tarball_between_default_axum_limit_and_max_upload_size.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.1.0", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__readme__new_krate_with_empty_readme.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__readme__new_krate_with_empty_readme.snap index 7a26f3ebda1..89576e0249d 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__readme__new_krate_with_empty_readme.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__readme__new_krate_with_empty_readme.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__readme__new_krate_with_readme.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__readme__new_krate_with_readme.snap index 7a26f3ebda1..89576e0249d 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__readme__new_krate_with_readme.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__readme__new_krate_with_readme.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0", "description": "description", "documentation": null, "downloads": 0, diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__readme__new_krate_with_readme_and_plus_version.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__readme__new_krate_with_readme_and_plus_version.snap index 8e4fca28bd0..727bd448fa1 100644 --- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__readme__new_krate_with_readme_and_plus_version.snap +++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__readme__new_krate_with_readme_and_plus_version.snap @@ -7,6 +7,7 @@ expression: response.json() "badges": null, "categories": null, "created_at": "[datetime]", + "default_version": "1.0.0+foo", "description": "description", "documentation": null, "downloads": 0, From 8d1ee6112bcb0ce407e02dd6c7683c4682045d8c Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Thu, 24 Oct 2024 18:57:54 +0800 Subject: [PATCH 6/7] views: Log a crate without a `default_version` using Sentry --- src/views.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/views.rs b/src/views.rs index a0cf1f02ad3..7db181c1790 100644 --- a/src/views.rs +++ b/src/views.rs @@ -258,6 +258,10 @@ impl EncodableCrate { let repository = remove_blocked_urls(repository); let default_version = default_version.map(ToString::to_string); + if default_version.is_none() { + let message = format!("Crate `{name}` has no default version"); + sentry::capture_message(&message, sentry::Level::Info); + } let max_version = top_versions .and_then(|v| v.highest.as_ref()) From 42567000a85a55d99a0e671d83c4a436f7537294 Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Thu, 24 Oct 2024 18:59:14 +0800 Subject: [PATCH 7/7] views: Always serialize the `default_version` field --- src/views.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/views.rs b/src/views.rs index 7db181c1790..42821f21647 100644 --- a/src/views.rs +++ b/src/views.rs @@ -208,7 +208,6 @@ pub struct EncodableCrate { // NOTE: Used by shields.io, altering `downloads` requires a PR with shields.io pub downloads: i64, pub recent_downloads: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub default_version: Option, // NOTE: Used by shields.io, altering `max_version` requires a PR with shields.io pub max_version: String,