From 5e2da2c82fccf4260a4f974c1b6fdb533ecc52a5 Mon Sep 17 00:00:00 2001 From: eth3lbert Date: Sun, 29 Dec 2024 19:11:21 +0800 Subject: [PATCH] controllers/krate/metadata: Add support for `default_version` include mode This allows us to respond with a version data of `default_version` included, which potentially benefits apps by eliminating the need to wait for the crate response to obtain the default version and then make a subsequent request to get the actual version data. This is particularly useful when we move towards not requesting with `versions` included. --- src/controllers/krate/metadata.rs | 27 ++++++- ..._io__openapi__tests__openapi_snapshot.snap | 2 +- src/tests/routes/crates/read.rs | 40 ++++++++++ ...crates__read__include_default_version.snap | 78 +++++++++++++++++++ ...tests__routes__crates__read__new_name.snap | 1 + ...io__tests__routes__crates__read__show.snap | 1 + ...routes__crates__read__show_all_yanked.snap | 1 + ...s__routes__crates__read__show_minimal.snap | 1 + 8 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__include_default_version.snap diff --git a/src/controllers/krate/metadata.rs b/src/controllers/krate/metadata.rs index 2b897ef3f8e..a0c1fa2b1a7 100644 --- a/src/controllers/krate/metadata.rs +++ b/src/controllers/krate/metadata.rs @@ -28,7 +28,7 @@ pub struct FindQueryParams { /// Additional data to include in the response. /// /// Valid values: `versions`, `keywords`, `categories`, `badges`, - /// `downloads`, or `full`. + /// `downloads`, `default_version`, or `full`. /// /// Defaults to `full` for backwards compatibility. /// @@ -118,6 +118,21 @@ pub async fn find_crate( .as_ref() .map(|vps| vps.iter().map(|v| v.0.id).collect()); + let mut default_version_meta = None; + if let Some(default_version) = default_version.as_ref().filter(|_| include.default_version) { + // Find the default_version from composed versions to minimize round trips when possible. + if let Some(versions) = versions_publishers_and_audit_actions.as_ref() { + default_version_meta = versions + .iter() + .find(|(version, _, _)| version.num.as_str() == default_version) + .cloned(); + } else if let Ok(version) = krate.find_version(&mut conn, default_version).await { + let published_by = version.published_by(&mut conn).await?; + let actions = VersionOwnerAction::by_version(&mut conn, &version).await?; + default_version_meta = Some((version, published_by, actions)); + } + }; + let kws = if include.keywords { Some( CrateKeyword::belonging_to(&krate) @@ -174,6 +189,8 @@ pub async fn find_crate( .map(|(v, pb, aas)| EncodableVersion::from(v, &krate.name, pb, aas)) .collect::>() }); + let encodable_default_version = + default_version_meta.map(|(v, pb, aas)| EncodableVersion::from(v, &krate.name, pb, aas)); let encodable_keywords = kws.map(|kws| { kws.into_iter() @@ -192,6 +209,7 @@ pub async fn find_crate( "versions": encodable_versions, "keywords": encodable_keywords, "categories": encodable_cats, + "default_version": encodable_default_version, })) } @@ -202,6 +220,7 @@ struct ShowIncludeMode { categories: bool, badges: bool, downloads: bool, + default_version: bool, } impl Default for ShowIncludeMode { @@ -213,13 +232,14 @@ impl Default for ShowIncludeMode { categories: true, badges: true, downloads: true, + default_version: false, } } } impl ShowIncludeMode { const INVALID_COMPONENT: &'static str = - "invalid component for ?include= (expected 'versions', 'keywords', 'categories', 'badges', 'downloads', or 'full')"; + "invalid component for ?include= (expected 'versions', 'keywords', 'categories', 'badges', 'downloads', 'default_version', or 'full')"; } impl FromStr for ShowIncludeMode { @@ -232,6 +252,7 @@ impl FromStr for ShowIncludeMode { categories: false, badges: false, downloads: false, + default_version: false, }; for component in s.split(',') { match component { @@ -243,6 +264,7 @@ impl FromStr for ShowIncludeMode { categories: true, badges: true, downloads: true, + default_version: true, } } "versions" => mode.versions = true, @@ -250,6 +272,7 @@ impl FromStr for ShowIncludeMode { "categories" => mode.categories = true, "badges" => mode.badges = true, "downloads" => mode.downloads = true, + "default_version" => mode.default_version = true, _ => return Err(bad_request(Self::INVALID_COMPONENT)), } } diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap index 71e86481062..4c58ab89199 100644 --- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap +++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot.snap @@ -523,7 +523,7 @@ snapshot_kind: text } }, { - "description": "Additional data to include in the response.\n\nValid values: `versions`, `keywords`, `categories`, `badges`,\n`downloads`, or `full`.\n\nDefaults to `full` for backwards compatibility.\n\nThis parameter expects a comma-separated list of values.", + "description": "Additional data to include in the response.\n\nValid values: `versions`, `keywords`, `categories`, `badges`,\n`downloads`, `default_version`, or `full`.\n\nDefaults to `full` for backwards compatibility.\n\nThis parameter expects a comma-separated list of values.", "in": "query", "name": "include", "required": false, diff --git a/src/tests/routes/crates/read.rs b/src/tests/routes/crates/read.rs index 7825c3b638d..c0777473c22 100644 --- a/src/tests/routes/crates/read.rs +++ b/src/tests/routes/crates/read.rs @@ -178,3 +178,43 @@ async fn test_new_name() { ".crate.updated_at" => "[datetime]", }); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_include_default_version() { + let (app, anon, user) = TestApp::init().with_user().await; + let mut conn = app.db_conn().await; + let user = user.as_model(); + + CrateBuilder::new("foo_default_version", 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")) + .version(VersionBuilder::new("0.5.1")) + .keyword("kw1") + .downloads(20) + .recent_downloads(10) + .expect_build(&mut conn) + .await; + + let response = anon + .get::<()>("/api/v1/crates/foo_default_version?include=default_version") + .await; + assert_eq!(response.status(), StatusCode::OK); + assert_json_snapshot!(response.json(), { + ".crate.created_at" => "[datetime]", + ".crate.updated_at" => "[datetime]", + ".default_version.created_at" => "[datetime]", + ".default_version.updated_at" => "[datetime]", + }); + + let response_with_versions = anon + .get::<()>("/api/v1/crates/foo_default_version?include=versions,default_version") + .await; + assert_eq!(response_with_versions.status(), StatusCode::OK); + assert_eq!( + response_with_versions.json().get("default_version"), + response.json().get("default_version") + ); +} diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__include_default_version.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__include_default_version.snap new file mode 100644 index 00000000000..c682ad318af --- /dev/null +++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__include_default_version.snap @@ -0,0 +1,78 @@ +--- +source: src/tests/routes/crates/read.rs +expression: response.json() +snapshot_kind: text +--- +{ + "categories": null, + "crate": { + "badges": [], + "categories": null, + "created_at": "[datetime]", + "default_version": "0.5.1", + "description": "description", + "documentation": "https://example.com", + "downloads": 20, + "exact_match": false, + "homepage": "http://example.com", + "id": "foo_default_version", + "keywords": null, + "links": { + "owner_team": "/api/v1/crates/foo_default_version/owner_team", + "owner_user": "/api/v1/crates/foo_default_version/owner_user", + "owners": "/api/v1/crates/foo_default_version/owners", + "reverse_dependencies": "/api/v1/crates/foo_default_version/reverse_dependencies", + "version_downloads": "/api/v1/crates/foo_default_version/downloads", + "versions": "/api/v1/crates/foo_default_version/versions" + }, + "max_stable_version": null, + "max_version": "0.0.0", + "name": "foo_default_version", + "newest_version": "0.0.0", + "recent_downloads": null, + "repository": null, + "updated_at": "[datetime]", + "versions": null, + "yanked": false + }, + "default_version": { + "audit_actions": [], + "bin_names": null, + "checksum": " ", + "crate": "foo_default_version", + "crate_size": 0, + "created_at": "[datetime]", + "description": null, + "dl_path": "/api/v1/crates/foo_default_version/0.5.1/download", + "documentation": null, + "downloads": 0, + "edition": null, + "features": {}, + "has_lib": null, + "homepage": null, + "id": 3, + "lib_links": null, + "license": null, + "links": { + "authors": "/api/v1/crates/foo_default_version/0.5.1/authors", + "dependencies": "/api/v1/crates/foo_default_version/0.5.1/dependencies", + "version_downloads": "/api/v1/crates/foo_default_version/0.5.1/downloads" + }, + "num": "0.5.1", + "published_by": { + "avatar": null, + "id": 1, + "login": "foo", + "name": null, + "url": "https://github.com/foo" + }, + "readme_path": "/api/v1/crates/foo_default_version/0.5.1/readme", + "repository": null, + "rust_version": null, + "updated_at": "[datetime]", + "yank_message": null, + "yanked": false + }, + "keywords": null, + "versions": null +} 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 ff4441359e4..275603ed067 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 @@ -35,6 +35,7 @@ snapshot_kind: text "versions": null, "yanked": false }, + "default_version": null, "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 286bb0b1842..fd4c3fa65e0 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 @@ -41,6 +41,7 @@ snapshot_kind: text ], "yanked": false }, + "default_version": null, "keywords": [ { "crates_cnt": 1, 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 index e64a14e9663..b1e7abd2d56 100644 --- 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 @@ -40,6 +40,7 @@ snapshot_kind: text ], "yanked": true }, + "default_version": null, "keywords": [ { "crates_cnt": 1, 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 60c1e24ccff..786b7be4df3 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 @@ -35,6 +35,7 @@ snapshot_kind: text "versions": null, "yanked": false }, + "default_version": null, "keywords": null, "versions": null }