Skip to content

Commit b9c7fff

Browse files
committed
Convert krate::show_minimal into a mode of krate::show
1 parent 0bb780b commit b9c7fff

File tree

6 files changed

+172
-97
lines changed

6 files changed

+172
-97
lines changed

src/controllers/krate/metadata.rs

Lines changed: 129 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! `Cargo.toml` file.
66
77
use std::cmp::Reverse;
8+
use std::str::FromStr;
89

910
use crate::controllers::frontend_prelude::*;
1011
use crate::controllers::helpers::pagination::PaginationOptions;
@@ -111,83 +112,145 @@ pub fn summary(req: &mut dyn RequestExt) -> EndpointResult {
111112
/// Handles the `GET /crates/:crate_id` route.
112113
pub fn show(req: &mut dyn RequestExt) -> EndpointResult {
113114
let name = &req.params()["crate_id"];
115+
let include = req
116+
.query()
117+
.get("include")
118+
.map(|mode| ShowIncludeMode::from_str(mode))
119+
.transpose()?
120+
.unwrap_or_default();
121+
114122
let conn = req.db_read_only()?;
115123
let krate: Crate = Crate::by_name(name).first(&*conn)?;
116124

117-
let mut versions_and_publishers: Vec<(Version, Option<User>)> = krate
118-
.all_versions()
119-
.left_outer_join(users::table)
120-
.select((versions::all_columns, users::all_columns.nullable()))
121-
.load(&*conn)?;
122-
123-
versions_and_publishers
124-
.sort_by_cached_key(|(version, _)| Reverse(semver::Version::parse(&version.num).ok()));
125-
126-
let versions = versions_and_publishers
127-
.iter()
128-
.map(|(v, _)| v)
129-
.cloned()
130-
.collect::<Vec<_>>();
131-
let versions_publishers_and_audit_actions = versions_and_publishers
132-
.into_iter()
133-
.zip(VersionOwnerAction::for_versions(&conn, &versions)?.into_iter())
134-
.map(|((v, pb), aas)| (v, pb, aas))
135-
.collect::<Vec<_>>();
125+
let versions_publishers_and_audit_actions = if include.full {
126+
let mut versions_and_publishers: Vec<(Version, Option<User>)> = krate
127+
.all_versions()
128+
.left_outer_join(users::table)
129+
.select((versions::all_columns, users::all_columns.nullable()))
130+
.load(&*conn)?;
131+
versions_and_publishers
132+
.sort_by_cached_key(|(version, _)| Reverse(semver::Version::parse(&version.num).ok()));
133+
134+
let versions = versions_and_publishers
135+
.iter()
136+
.map(|(v, _)| v)
137+
.cloned()
138+
.collect::<Vec<_>>();
139+
Some(
140+
versions_and_publishers
141+
.into_iter()
142+
.zip(VersionOwnerAction::for_versions(&conn, &versions)?.into_iter())
143+
.map(|((v, pb), aas)| (v, pb, aas))
144+
.collect::<Vec<_>>(),
145+
)
146+
} else {
147+
None
148+
};
136149
let ids = versions_publishers_and_audit_actions
137-
.iter()
138-
.map(|v| v.0.id)
139-
.collect();
140-
141-
let kws = CrateKeyword::belonging_to(&krate)
142-
.inner_join(keywords::table)
143-
.select(keywords::all_columns)
144-
.load(&*conn)?;
145-
let cats = CrateCategory::belonging_to(&krate)
146-
.inner_join(categories::table)
147-
.select(categories::all_columns)
148-
.load(&*conn)?;
149-
let recent_downloads = RecentCrateDownloads::belonging_to(&krate)
150-
.select(recent_crate_downloads::downloads)
151-
.get_result(&*conn)
152-
.optional()?;
150+
.as_ref()
151+
.map(|vps| vps.iter().map(|v| v.0.id).collect());
152+
153+
let kws = if include.full {
154+
Some(
155+
CrateKeyword::belonging_to(&krate)
156+
.inner_join(keywords::table)
157+
.select(keywords::all_columns)
158+
.load(&*conn)?,
159+
)
160+
} else {
161+
None
162+
};
163+
let cats = if include.full {
164+
Some(
165+
CrateCategory::belonging_to(&krate)
166+
.inner_join(categories::table)
167+
.select(categories::all_columns)
168+
.load(&*conn)?,
169+
)
170+
} else {
171+
None
172+
};
173+
let recent_downloads = if include.full {
174+
RecentCrateDownloads::belonging_to(&krate)
175+
.select(recent_crate_downloads::downloads)
176+
.get_result(&*conn)
177+
.optional()?
178+
} else {
179+
None
180+
};
153181

154-
let badges = badges::table
155-
.filter(badges::crate_id.eq(krate.id))
156-
.load(&*conn)?;
157-
let top_versions = krate.top_versions(&conn)?;
182+
let badges = if include.full {
183+
Some(
184+
badges::table
185+
.filter(badges::crate_id.eq(krate.id))
186+
.load(&*conn)?,
187+
)
188+
} else {
189+
None
190+
};
191+
let top_versions = if include.full {
192+
Some(krate.top_versions(&conn)?)
193+
} else {
194+
None
195+
};
158196

159-
Ok(req.json(&json!({
160-
"crate": EncodableCrate::from(
161-
krate.clone(),
162-
Some(&top_versions),
163-
Some(ids),
164-
Some(&kws),
165-
Some(&cats),
166-
Some(badges),
167-
false,
168-
recent_downloads,
169-
),
170-
"versions": versions_publishers_and_audit_actions
171-
.into_iter()
197+
let encodable_crate = EncodableCrate::from(
198+
krate.clone(),
199+
top_versions.as_ref(),
200+
ids,
201+
kws.as_deref(),
202+
cats.as_deref(),
203+
badges,
204+
false,
205+
recent_downloads,
206+
);
207+
let encodable_versions = versions_publishers_and_audit_actions.map(|vpa| {
208+
vpa.into_iter()
172209
.map(|(v, pb, aas)| EncodableVersion::from(v, &krate.name, pb, aas))
173-
.collect::<Vec<_>>(),
174-
"keywords": kws.into_iter().map(Keyword::into).collect::<Vec<EncodableKeyword>>(),
175-
"categories": cats.into_iter().map(Category::into).collect::<Vec<EncodableCategory>>(),
210+
.collect::<Vec<_>>()
211+
});
212+
let encodable_keywords = kws.map(|kws| {
213+
kws.into_iter()
214+
.map(Keyword::into)
215+
.collect::<Vec<EncodableKeyword>>()
216+
});
217+
let encodable_cats = cats.map(|cats| {
218+
cats.into_iter()
219+
.map(Category::into)
220+
.collect::<Vec<EncodableCategory>>()
221+
});
222+
Ok(req.json(&json!({
223+
"crate": encodable_crate,
224+
"versions": encodable_versions,
225+
"keywords": encodable_keywords,
226+
"categories": encodable_cats,
176227
})))
177228
}
178229

179-
/// Handles the `GET /crates/:crate_id/crate` route.
180-
///
181-
/// A minimal version of [`show`] that only covers the crate itself, without versions or catalog information
182-
/// (such as keywords and categories).
183-
pub fn show_minimal(req: &mut dyn RequestExt) -> EndpointResult {
184-
let name = &req.params()["crate_id"];
185-
let conn = req.db_read_only()?;
186-
let krate: Crate = Crate::by_name(name).first(&*conn)?;
230+
#[derive(Debug)]
231+
struct ShowIncludeMode {
232+
full: bool,
233+
}
187234

188-
Ok(req.json(&EncodableCrate::from_minimal(
189-
krate, None, None, false, None,
190-
)))
235+
impl Default for ShowIncludeMode {
236+
fn default() -> Self {
237+
// Send everything for legacy clients that expect the full response
238+
Self { full: true }
239+
}
240+
}
241+
242+
impl FromStr for ShowIncludeMode {
243+
type Err = Box<dyn AppError>;
244+
245+
fn from_str(s: &str) -> Result<Self, Self::Err> {
246+
match s {
247+
"" => Ok(ShowIncludeMode { full: false }),
248+
"full" => Ok(ShowIncludeMode { full: true }),
249+
_ => Err(bad_request(
250+
"invalid value for ?mode= (expected '' or 'full')",
251+
)),
252+
}
253+
}
191254
}
192255

193256
/// Handles the `GET /crates/:crate_id/:version/readme` route.

src/router.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ pub fn build_router(app: &App) -> RouteBuilder {
3636

3737
// Routes used by the frontend
3838
api_router.get("/crates/:crate_id", C(krate::metadata::show));
39-
api_router.get("/crates/:crate_id/crate", C(krate::metadata::show_minimal));
4039
api_router.get("/crates/:crate_id/:version", C(version::metadata::show));
4140
api_router.get(
4241
"/crates/:crate_id/:version/readme",

src/tests/all.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ struct CrateMeta {
6969
pub struct CrateResponse {
7070
#[serde(rename = "crate")]
7171
krate: EncodableCrate,
72-
versions: Vec<EncodableVersion>,
73-
keywords: Vec<EncodableKeyword>,
72+
versions: Option<Vec<EncodableVersion>>,
73+
keywords: Option<Vec<EncodableKeyword>>,
7474
}
7575
#[derive(Deserialize)]
7676
pub struct VersionResponse {

src/tests/krate/show.rs

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,29 @@ fn show() {
4343
assert_eq!(json.krate.documentation, krate.documentation);
4444
assert_eq!(json.krate.keywords, Some(vec!["kw1".into()]));
4545
assert_eq!(json.krate.recent_downloads, Some(10));
46-
let versions = json.krate.versions.as_ref().unwrap();
46+
let crate_versions = json.krate.versions.as_ref().unwrap();
47+
assert_eq!(crate_versions.len(), 3);
48+
let versions = json.versions.as_ref().unwrap();
4749
assert_eq!(versions.len(), 3);
48-
assert_eq!(json.versions.len(), 3);
4950

50-
assert_eq!(json.versions[0].id, versions[0]);
51-
assert_eq!(json.versions[0].krate, json.krate.id);
52-
assert_eq!(json.versions[0].num, "1.0.0");
53-
assert_none!(&json.versions[0].published_by);
51+
assert_eq!(versions[0].id, crate_versions[0]);
52+
assert_eq!(versions[0].krate, json.krate.id);
53+
assert_eq!(versions[0].num, "1.0.0");
54+
assert_none!(&versions[0].published_by);
5455
let suffix = "/api/v1/crates/foo_show/1.0.0/download";
5556
assert!(
56-
json.versions[0].dl_path.ends_with(suffix),
57+
versions[0].dl_path.ends_with(suffix),
5758
"bad suffix {}",
58-
json.versions[0].dl_path
59+
versions[0].dl_path
5960
);
60-
assert_eq!(1, json.keywords.len());
61-
assert_eq!("kw1", json.keywords[0].id);
61+
let keywords = json.keywords.as_ref().unwrap();
62+
assert_eq!(1, keywords.len());
63+
assert_eq!("kw1", keywords[0].id);
6264

63-
assert_eq!(json.versions[1].num, "0.5.1");
64-
assert_eq!(json.versions[2].num, "0.5.0");
65+
assert_eq!(versions[1].num, "0.5.1");
66+
assert_eq!(versions[2].num, "0.5.0");
6567
assert_eq!(
66-
json.versions[1].published_by.as_ref().unwrap().login,
68+
versions[1].published_by.as_ref().unwrap().login,
6769
user.gh_login
6870
);
6971
}
@@ -102,14 +104,16 @@ fn show_minimal() {
102104
});
103105

104106
let json = anon.show_crate_minimal("foo_show_minimal");
105-
assert_eq!(json.name, krate.name);
106-
assert_eq!(json.id, krate.name);
107-
assert_eq!(json.description, krate.description);
108-
assert_eq!(json.homepage, krate.homepage);
109-
assert_eq!(json.documentation, krate.documentation);
110-
assert_eq!(json.keywords, None);
111-
assert_eq!(json.recent_downloads, None);
112-
assert_eq!(json.versions, None);
107+
assert_eq!(json.krate.name, krate.name);
108+
assert_eq!(json.krate.id, krate.name);
109+
assert_eq!(json.krate.description, krate.description);
110+
assert_eq!(json.krate.homepage, krate.homepage);
111+
assert_eq!(json.krate.documentation, krate.documentation);
112+
assert_eq!(json.krate.keywords, None);
113+
assert_eq!(json.krate.recent_downloads, None);
114+
assert_eq!(json.krate.versions, None);
115+
assert!(json.versions.is_none());
116+
assert!(json.keywords.is_none());
113117
}
114118

115119
#[test]

src/tests/util.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ use crate::{
2323
builders::PublishBuilder, CategoryListResponse, CategoryResponse, CrateList, CrateResponse,
2424
GoodCrate, OkBool, OwnersResponse, VersionResponse,
2525
};
26-
use cargo_registry::{
27-
models::{ApiToken, CreatedApiToken, User},
28-
views::EncodableCrate,
29-
};
26+
use cargo_registry::models::{ApiToken, CreatedApiToken, User};
3027

3128
use conduit::{BoxError, Handler, Method};
3229
use conduit_cookie::SessionMiddleware;
@@ -106,6 +103,14 @@ pub trait RequestHelper {
106103
self.run(self.get_request(path))
107104
}
108105

106+
/// Issue a GET request with a query string
107+
#[track_caller]
108+
fn get_query<T>(&self, path: &str, query: &str) -> Response<T> {
109+
let mut req = self.get_request(path);
110+
req.with_query(query);
111+
self.run(req)
112+
}
113+
109114
/// Issue a GET request that includes query parameters
110115
#[track_caller]
111116
fn get_with_query<T>(&self, path: &str, query: &str) -> Response<T> {
@@ -166,9 +171,9 @@ pub trait RequestHelper {
166171
}
167172

168173
/// Request the JSON used for a crate's minimal page
169-
fn show_crate_minimal(&self, krate_name: &str) -> EncodableCrate {
170-
let url = format!("/api/v1/crates/{krate_name}/crate");
171-
self.get(&url).good()
174+
fn show_crate_minimal(&self, krate_name: &str) -> CrateResponse {
175+
let url = format!("/api/v1/crates/{krate_name}");
176+
self.get_query(&url, "include=").good()
172177
}
173178

174179
/// Request the JSON used to list a crate's owners

src/tests/version.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,17 @@ fn version_size() {
150150

151151
let version1 = crate_json
152152
.versions
153+
.as_ref()
154+
.unwrap()
153155
.iter()
154156
.find(|v| v.num == "1.0.0")
155157
.expect("Could not find v1.0.0");
156158
assert_eq!(version1.crate_size, Some(35));
157159

158160
let version2 = crate_json
159161
.versions
162+
.as_ref()
163+
.unwrap()
160164
.iter()
161165
.find(|v| v.num == "2.0.0")
162166
.expect("Could not find v2.0.0");

0 commit comments

Comments
 (0)