Skip to content

Commit dcbe681

Browse files
committed
Implement GET /crates/:crate_id/maintenance.svg endpoint
1 parent 16460d2 commit dcbe681

File tree

6 files changed

+149
-0
lines changed

6 files changed

+149
-0
lines changed

src/controllers/krate.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod badges;
12
pub mod downloads;
23
pub mod follow;
34
pub mod metadata;

src/controllers/krate/badges.rs

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//! Endpoints that provide badges based on crate metadata
2+
3+
use crate::controllers::frontend_prelude::*;
4+
5+
use crate::models::{Badge, Crate, CrateBadge, MaintenanceStatus};
6+
use crate::schema::*;
7+
8+
use conduit::{Body, Response};
9+
10+
/// Handles the `GET /crates/:crate_id/maintenance.svg` route.
11+
pub fn maintenance(req: &mut dyn RequestExt) -> EndpointResult {
12+
let name = &req.params()["crate_id"];
13+
let conn = req.db_read_only()?;
14+
15+
let krate = Crate::by_name(name).first::<Crate>(&*conn);
16+
if krate.is_err() {
17+
let response = Response::builder().status(404).body(Body::empty()).unwrap();
18+
19+
return Ok(response);
20+
}
21+
22+
let krate = krate.unwrap();
23+
24+
let maintenance_badge = CrateBadge::belonging_to(&krate)
25+
.select((badges::crate_id, badges::all_columns))
26+
.load::<CrateBadge>(&*conn)?
27+
.into_iter()
28+
.find(|cb| match cb.badge {
29+
Badge::Maintenance { .. } => true,
30+
_ => false,
31+
});
32+
33+
if maintenance_badge.is_none() {
34+
return Ok(req.redirect(
35+
"https://img.shields.io/badge/maintenance-unknown-lightgrey.svg".to_owned(),
36+
));
37+
}
38+
39+
let status = match maintenance_badge {
40+
Some(CrateBadge {
41+
badge: Badge::Maintenance { status },
42+
..
43+
}) => Some(status),
44+
_ => None,
45+
};
46+
47+
let status = status.unwrap();
48+
49+
let message = match status {
50+
MaintenanceStatus::ActivelyDeveloped => "actively--developed",
51+
MaintenanceStatus::PassivelyMaintained => "passively--maintained",
52+
MaintenanceStatus::AsIs => "as--is",
53+
MaintenanceStatus::None => "unknown",
54+
MaintenanceStatus::Experimental => "experimental",
55+
MaintenanceStatus::LookingForMaintainer => "looking--for--maintainer",
56+
MaintenanceStatus::Deprecated => "deprecated",
57+
};
58+
59+
let color = match status {
60+
MaintenanceStatus::ActivelyDeveloped => "brightgreen",
61+
MaintenanceStatus::PassivelyMaintained => "yellowgreen",
62+
MaintenanceStatus::AsIs => "yellow",
63+
MaintenanceStatus::None => "lightgrey",
64+
MaintenanceStatus::Experimental => "blue",
65+
MaintenanceStatus::LookingForMaintainer => "orange",
66+
MaintenanceStatus::Deprecated => "red",
67+
};
68+
69+
let url = format!(
70+
"https://img.shields.io/badge/maintenance-{}-{}.svg",
71+
message, color
72+
);
73+
Ok(req.redirect(url))
74+
}

src/router.rs

+4
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ pub fn build_router(app: &App) -> R404 {
6666
"/crates/:crate_id/reverse_dependencies",
6767
C(krate::metadata::reverse_dependencies),
6868
);
69+
api_router.get(
70+
"/crates/:crate_id/maintenance.svg",
71+
C(krate::badges::maintenance),
72+
);
6973
api_router.get("/keywords", C(keyword::index));
7074
api_router.get("/keywords/:keyword_id", C(keyword::show));
7175
api_router.get("/categories", C(category::index));

src/tests/all.rs

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ mod dump_db;
5151
mod git;
5252
mod keyword;
5353
mod krate;
54+
mod maintenance_badge;
5455
mod owners;
5556
mod read_only_mode;
5657
mod record;

src/tests/maintenance_badge.rs

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use std::collections::HashMap;
2+
3+
use cargo_registry::models::Badge;
4+
use conduit::StatusCode;
5+
6+
use crate::util::{MockAnonymousUser, RequestHelper};
7+
use crate::{builders::CrateBuilder, TestApp};
8+
9+
fn set_up() -> MockAnonymousUser {
10+
let (app, anon, user) = TestApp::init().with_user();
11+
let user = user.as_model();
12+
13+
app.db(|conn| {
14+
let mut badges = HashMap::new();
15+
badges.insert("maintenance".to_owned(), {
16+
let mut attributes = HashMap::new();
17+
attributes.insert("status".to_owned(), "looking-for-maintainer".to_owned());
18+
attributes
19+
});
20+
21+
let krate = CrateBuilder::new("foo", user.id).expect_build(conn);
22+
Badge::update_crate(conn, &krate, Some(&badges)).unwrap();
23+
24+
CrateBuilder::new("bar", user.id).expect_build(conn);
25+
});
26+
27+
anon
28+
}
29+
30+
#[test]
31+
fn crate_with_maintenance_badge() {
32+
let anon = set_up();
33+
34+
anon.get::<()>("/api/v1/crates/foo/maintenance.svg")
35+
.assert_status(StatusCode::FOUND)
36+
.assert_redirects_to(
37+
"https://img.shields.io/badge/maintenance-looking--for--maintainer-orange.svg",
38+
);
39+
}
40+
41+
#[test]
42+
fn crate_without_maintenance_badge() {
43+
let anon = set_up();
44+
45+
anon.get::<()>("/api/v1/crates/bar/maintenance.svg")
46+
.assert_status(StatusCode::FOUND)
47+
.assert_redirects_to("https://img.shields.io/badge/maintenance-unknown-lightgrey.svg");
48+
}
49+
50+
#[test]
51+
fn unknown_crate() {
52+
let anon = set_up();
53+
54+
anon.get::<()>("/api/v1/crates/unknown/maintenance.svg")
55+
.assert_status(StatusCode::NOT_FOUND);
56+
}

src/tests/util.rs

+13
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,19 @@ where
627627
.ends_with(target));
628628
self
629629
}
630+
631+
pub fn assert_redirects_to(&self, target: &str) -> &Self {
632+
assert_eq!(
633+
self.response
634+
.headers()
635+
.get(header::LOCATION)
636+
.unwrap()
637+
.to_str()
638+
.unwrap(),
639+
target
640+
);
641+
self
642+
}
630643
}
631644

632645
impl Response<()> {

0 commit comments

Comments
 (0)