Skip to content

Commit 78db1a9

Browse files
committed
Add a include_yanked parameter to /api/v1/crates
Add a URL parameter to the `/api/v1/crates` endpoint that specifies whether or not the endpoint should return crates that have been fully yanked - i.e., where every version has been yanked. If not specified, this defaults to `true` because we have typically always returned all matching crates, whether or not their versions have been yanked. This makes it possible to display only non-yanked crates at various places in the UI, and is especially intended for the user and team pages to display only non-yanked crates, as discussed in #958.
1 parent c754c10 commit 78db1a9

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

src/controllers/krate/search.rs

+21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Endpoint for searching and discovery functionality
22
3+
use diesel::dsl::*;
34
use diesel_full_text_search::*;
45

56
use crate::controllers::helpers::Paginate;
@@ -41,6 +42,10 @@ pub fn search(req: &mut dyn Request) -> CargoResult<Response> {
4142
.get("sort")
4243
.map(|s| &**s)
4344
.unwrap_or("recent-downloads");
45+
let include_yanked = params
46+
.get("include_yanked")
47+
.map(|s| url_boolean(s))
48+
.unwrap_or(true);
4449

4550
let mut query = crates::table
4651
.left_join(recent_crate_downloads::table)
@@ -139,6 +144,14 @@ pub fn search(req: &mut dyn Request) -> CargoResult<Response> {
139144
);
140145
}
141146

147+
if !include_yanked {
148+
query = query.filter(exists(
149+
versions::table
150+
.filter(versions::crate_id.eq(crates::id))
151+
.filter(versions::yanked.eq(false)),
152+
));
153+
}
154+
142155
if sort == "downloads" {
143156
query = query.then_order_by(crates::downloads.desc())
144157
} else if sort == "recent-downloads" {
@@ -208,3 +221,11 @@ pub fn search(req: &mut dyn Request) -> CargoResult<Response> {
208221
meta: Meta { total },
209222
}))
210223
}
224+
225+
/// Takes a string that might represent a boolean in a URL and converts it into a boolean.
226+
fn url_boolean(value: &str) -> bool {
227+
match value.to_lowercase().as_ref() {
228+
"y" | "yes" | "t" | "true" | "1" => true,
229+
_ => false,
230+
}
231+
}

src/tests/krate.rs

+43
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,49 @@ fn exact_match_on_queries_with_sort() {
414414
assert_eq!(json.crates[3].name, "other_sort");
415415
}
416416

417+
#[test]
418+
fn index_include_yanked() {
419+
let (app, anon, user) = TestApp::init().with_user();
420+
let user = user.as_model();
421+
422+
app.db(|conn| {
423+
CrateBuilder::new("unyanked", user.id)
424+
.version(VersionBuilder::new("1.0.0"))
425+
.version(VersionBuilder::new("2.0.0"))
426+
.expect_build(conn);
427+
428+
CrateBuilder::new("newest_yanked", user.id)
429+
.version(VersionBuilder::new("1.0.0"))
430+
.version(VersionBuilder::new("2.0.0").yanked(true))
431+
.expect_build(conn);
432+
433+
CrateBuilder::new("oldest_yanked", user.id)
434+
.version(VersionBuilder::new("1.0.0").yanked(true))
435+
.version(VersionBuilder::new("2.0.0"))
436+
.expect_build(conn);
437+
438+
CrateBuilder::new("all_yanked", user.id)
439+
.version(VersionBuilder::new("1.0.0").yanked(true))
440+
.version(VersionBuilder::new("2.0.0").yanked(true))
441+
.expect_build(conn);
442+
});
443+
444+
// Include fully yanked (all versions were yanked) crates
445+
let json = anon.search("include_yanked=y&sort=alphabetical");
446+
assert_eq!(json.meta.total, 4);
447+
assert_eq!(json.crates[0].name, "all_yanked");
448+
assert_eq!(json.crates[1].name, "newest_yanked");
449+
assert_eq!(json.crates[2].name, "oldest_yanked");
450+
assert_eq!(json.crates[3].name, "unyanked");
451+
452+
// Do not include fully yanked (all versions were yanked) crates
453+
let json = anon.search("include_yanked=n&sort=alphabetical");
454+
assert_eq!(json.meta.total, 3);
455+
assert_eq!(json.crates[0].name, "newest_yanked");
456+
assert_eq!(json.crates[1].name, "oldest_yanked");
457+
assert_eq!(json.crates[2].name, "unyanked");
458+
}
459+
417460
#[test]
418461
fn show() {
419462
let (app, anon, user) = TestApp::init().with_user();

0 commit comments

Comments
 (0)