Skip to content

Commit

Permalink
controllers/krate/metadata: Add support for default_version include…
Browse files Browse the repository at this point in the history
… mode (#10288)

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.
  • Loading branch information
eth3lbert authored Dec 31, 2024
1 parent 0602f95 commit 9b89b10
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 4 deletions.
25 changes: 22 additions & 3 deletions src/controllers/krate/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ 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.
///
/// **Note**: `versions` and `default_version` share the same key `versions`, therefore `default_version` will be ignored if both are provided.
///
/// This parameter expects a comma-separated list of values.
include: Option<String>,
}
Expand Down Expand Up @@ -88,7 +90,7 @@ pub async fn find_crate(
.optional()?
.ok_or_else(|| crate_not_found(&path.name))?;

let versions_publishers_and_audit_actions = if include.versions {
let mut versions_publishers_and_audit_actions = if include.versions {
let mut versions_and_publishers: Vec<(Version, Option<User>)> =
Version::belonging_to(&krate)
.left_outer_join(users::table)
Expand Down Expand Up @@ -118,6 +120,18 @@ pub async fn find_crate(
.as_ref()
.map(|vps| vps.iter().map(|v| v.0.id).collect());

// Since `versions` and `default_version` share the same key (versions), we should only settle
// the `default_version` when `versions` is not included.
if let Some(default_version) = default_version
.as_ref()
.filter(|_| include.default_version && !include.versions)
{
let 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?;
versions_publishers_and_audit_actions = Some(vec![(version, published_by, actions)]);
};

let kws = if include.keywords {
Some(
CrateKeyword::belonging_to(&krate)
Expand Down Expand Up @@ -202,6 +216,7 @@ struct ShowIncludeMode {
categories: bool,
badges: bool,
downloads: bool,
default_version: bool,
}

impl Default for ShowIncludeMode {
Expand All @@ -213,13 +228,14 @@ impl Default for ShowIncludeMode {
categories: true,
badges: true,
downloads: true,
default_version: true,
}
}
}

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 {
Expand All @@ -232,6 +248,7 @@ impl FromStr for ShowIncludeMode {
categories: false,
badges: false,
downloads: false,
default_version: false,
};
for component in s.split(',') {
match component {
Expand All @@ -243,13 +260,15 @@ impl FromStr for ShowIncludeMode {
categories: true,
badges: true,
downloads: true,
default_version: true,
}
}
"versions" => mode.versions = true,
"keywords" => mode.keywords = true,
"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)),
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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\n**Note**: `versions` and `default_version` share the same key `versions`, therefore `default_version` will be ignored if both are provided.\n\nThis parameter expects a comma-separated list of values.",
"in": "query",
"name": "include",
"required": false,
Expand Down
41 changes: 41 additions & 0 deletions src/tests/routes/crates/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,44 @@ 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]",
".versions[].created_at" => "[datetime]",
".versions[].updated_at" => "[datetime]",
});

let resp_versions = anon
.get::<()>("/api/v1/crates/foo_default_version?include=versions")
.await;
let resp_both = anon
.get::<()>("/api/v1/crates/foo_default_version?include=versions,default_version")
.await;
assert_eq!(resp_versions.status(), StatusCode::OK);
assert_eq!(resp_both.status(), StatusCode::OK);
assert_eq!(resp_versions.json(), resp_both.json());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
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
},
"keywords": null,
"versions": [
{
"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
}
]
}

0 comments on commit 9b89b10

Please sign in to comment.