Skip to content

Commit aa1833a

Browse files
committed
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.
1 parent f027ea1 commit aa1833a

File tree

2 files changed

+64
-1
lines changed

2 files changed

+64
-1
lines changed

src/controllers/krate/metadata.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,21 @@ pub async fn find_crate(
118118
.as_ref()
119119
.map(|vps| vps.iter().map(|v| v.0.id).collect());
120120

121+
let mut default_version_meta = None;
122+
if let Some(default_version) = default_version.as_ref().filter(|_| include.default_version) {
123+
// Find the default_version from composed versions to minimize round trips when possible.
124+
if let Some(versions) = versions_publishers_and_audit_actions.as_ref() {
125+
default_version_meta = versions
126+
.iter()
127+
.find(|(version, _, _)| version.num.as_str() == default_version)
128+
.cloned();
129+
} else if let Ok(version) = krate.find_version(&mut conn, default_version).await {
130+
let published_by = version.published_by(&mut conn).await?;
131+
let actions = VersionOwnerAction::by_version(&mut conn, &version).await?;
132+
default_version_meta = Some((version, published_by, actions));
133+
}
134+
};
135+
121136
let kws = if include.keywords {
122137
Some(
123138
CrateKeyword::belonging_to(&krate)
@@ -174,6 +189,8 @@ pub async fn find_crate(
174189
.map(|(v, pb, aas)| EncodableVersion::from(v, &krate.name, pb, aas))
175190
.collect::<Vec<_>>()
176191
});
192+
let encodable_default_version =
193+
default_version_meta.map(|(v, pb, aas)| EncodableVersion::from(v, &krate.name, pb, aas));
177194

178195
let encodable_keywords = kws.map(|kws| {
179196
kws.into_iter()
@@ -192,6 +209,7 @@ pub async fn find_crate(
192209
"versions": encodable_versions,
193210
"keywords": encodable_keywords,
194211
"categories": encodable_cats,
212+
"default_version": encodable_default_version,
195213
}))
196214
}
197215

@@ -202,6 +220,7 @@ struct ShowIncludeMode {
202220
categories: bool,
203221
badges: bool,
204222
downloads: bool,
223+
default_version: bool,
205224
}
206225

207226
impl Default for ShowIncludeMode {
@@ -213,13 +232,14 @@ impl Default for ShowIncludeMode {
213232
categories: true,
214233
badges: true,
215234
downloads: true,
235+
default_version: false,
216236
}
217237
}
218238
}
219239

220240
impl ShowIncludeMode {
221241
const INVALID_COMPONENT: &'static str =
222-
"invalid component for ?include= (expected 'versions', 'keywords', 'categories', 'badges', 'downloads', or 'full')";
242+
"invalid component for ?include= (expected 'versions', 'keywords', 'categories', 'badges', 'downloads', 'default_version', or 'full')";
223243
}
224244

225245
impl FromStr for ShowIncludeMode {
@@ -232,6 +252,7 @@ impl FromStr for ShowIncludeMode {
232252
categories: false,
233253
badges: false,
234254
downloads: false,
255+
default_version: false,
235256
};
236257
for component in s.split(',') {
237258
match component {
@@ -243,13 +264,15 @@ impl FromStr for ShowIncludeMode {
243264
categories: true,
244265
badges: true,
245266
downloads: true,
267+
default_version: true,
246268
}
247269
}
248270
"versions" => mode.versions = true,
249271
"keywords" => mode.keywords = true,
250272
"categories" => mode.categories = true,
251273
"badges" => mode.badges = true,
252274
"downloads" => mode.downloads = true,
275+
"default_version" => mode.default_version = true,
253276
_ => return Err(bad_request(Self::INVALID_COMPONENT)),
254277
}
255278
}

src/tests/routes/crates/read.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,43 @@ async fn test_new_name() {
178178
".crate.updated_at" => "[datetime]",
179179
});
180180
}
181+
182+
#[tokio::test(flavor = "multi_thread")]
183+
async fn test_include_default_version() {
184+
let (app, anon, user) = TestApp::init().with_user().await;
185+
let mut conn = app.db_conn().await;
186+
let user = user.as_model();
187+
188+
CrateBuilder::new("foo_default_version", user.id)
189+
.description("description")
190+
.documentation("https://example.com")
191+
.homepage("http://example.com")
192+
.version(VersionBuilder::new("1.0.0").yanked(true))
193+
.version(VersionBuilder::new("0.5.0"))
194+
.version(VersionBuilder::new("0.5.1"))
195+
.keyword("kw1")
196+
.downloads(20)
197+
.recent_downloads(10)
198+
.expect_build(&mut conn)
199+
.await;
200+
201+
let response = anon
202+
.get::<()>("/api/v1/crates/foo_default_version?include=default_version")
203+
.await;
204+
assert_eq!(response.status(), StatusCode::OK);
205+
assert_json_snapshot!(response.json(), {
206+
".crate.created_at" => "[datetime]",
207+
".crate.updated_at" => "[datetime]",
208+
".default_version.created_at" => "[datetime]",
209+
".default_version.updated_at" => "[datetime]",
210+
});
211+
212+
let response_with_versions = anon
213+
.get::<()>("/api/v1/crates/foo_default_version?include=versions,default_version")
214+
.await;
215+
assert_eq!(response_with_versions.status(), StatusCode::OK);
216+
assert_eq!(
217+
response_with_versions.json().get("default_version"),
218+
response.json().get("default_version")
219+
);
220+
}

0 commit comments

Comments
 (0)