Skip to content

Commit 04eb1b3

Browse files
committed
Add new endpoint giving simple rustdoc status for a version
1 parent d7b5f78 commit 04eb1b3

File tree

4 files changed

+160
-0
lines changed

4 files changed

+160
-0
lines changed

src/web/metrics.rs

+4
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ mod tests {
177177
"/crate/rcc/0.0.0/builds.json",
178178
"/crate/:name/:version/builds.json",
179179
),
180+
(
181+
"/crate/rcc/0.0.0/status.json",
182+
"/crate/:name/:version/status.json",
183+
),
180184
("/-/static/index.js", "static resource"),
181185
("/-/static/menu.js", "static resource"),
182186
("/-/static/keyboard.js", "static resource"),

src/web/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ mod rustdoc;
2727
mod sitemap;
2828
mod source;
2929
mod statics;
30+
mod status;
3031

3132
use crate::{db::Pool, impl_axum_webpage, Context};
3233
use anyhow::Error;
@@ -115,6 +116,14 @@ impl MatchSemver {
115116
| MatchSemver::Latest((v, i)) => (v, i),
116117
}
117118
}
119+
120+
/// If the matched version was an exact match to a semver version, returns the
121+
/// version string and id for the query. If the lookup required a semver match, returns
122+
/// `VersionNotFound`.
123+
fn assume_exact(self) -> Result<(String, i32), AxumNope> {
124+
let MatchSemver::Exact(details) = self else { return Err(AxumNope::VersionNotFound) };
125+
Ok(details)
126+
}
118127
}
119128

120129
/// Checks the database for crate releases that match the given name and version.

src/web/routes.rs

+4
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ pub(super) fn build_axum_routes() -> AxumRouter {
220220
"/crate/:name/:version/builds.json",
221221
get_internal(super::builds::build_list_json_handler),
222222
)
223+
.route(
224+
"/crate/:name/:version/status.json",
225+
get_internal(super::status::status_handler),
226+
)
223227
.route_with_tsr(
224228
"/crate/:name/:version/builds/:id",
225229
get_internal(super::build_details::build_details_handler),

src/web/status.rs

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
use super::cache::CachePolicy;
2+
use crate::{
3+
db::Pool,
4+
utils::spawn_blocking,
5+
web::{error::AxumResult, match_version_axum},
6+
};
7+
use axum::{
8+
extract::{Extension, Path},
9+
http::header::ACCESS_CONTROL_ALLOW_ORIGIN,
10+
response::IntoResponse,
11+
Json,
12+
};
13+
14+
pub(crate) async fn status_handler(
15+
Path((name, req_version)): Path<(String, String)>,
16+
Extension(pool): Extension<Pool>,
17+
) -> AxumResult<impl IntoResponse> {
18+
let (_, id) = match_version_axum(&pool, &name, Some(&req_version))
19+
.await?
20+
.assume_exact()?
21+
.assume_exact()?;
22+
23+
let rustdoc_status: bool = spawn_blocking({
24+
move || {
25+
Ok(pool
26+
.get()?
27+
.query_one(
28+
"SELECT releases.rustdoc_status
29+
FROM releases
30+
WHERE releases.id = $1
31+
",
32+
&[&id],
33+
)?
34+
.get("rustdoc_status"))
35+
}
36+
})
37+
.await?;
38+
39+
Ok((
40+
Extension(CachePolicy::NoStoreMustRevalidate),
41+
[(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
42+
Json(serde_json::json!({ "doc_status": rustdoc_status })),
43+
))
44+
}
45+
46+
#[cfg(test)]
47+
mod tests {
48+
use crate::{
49+
test::{assert_cache_control, wrapper},
50+
web::cache::CachePolicy,
51+
};
52+
use reqwest::StatusCode;
53+
54+
#[test]
55+
fn success() {
56+
wrapper(|env| {
57+
env.fake_release().name("foo").version("0.1.0").create()?;
58+
59+
let response = env.frontend().get("/crate/foo/0.1.0/status.json").send()?;
60+
assert_cache_control(&response, CachePolicy::NoStoreMustRevalidate, &env.config());
61+
assert_eq!(response.headers()["access-control-allow-origin"], "*");
62+
let value: serde_json::Value = serde_json::from_str(&response.text()?)?;
63+
64+
assert_eq!(value, serde_json::json!({"doc_status": true}));
65+
66+
Ok(())
67+
});
68+
}
69+
70+
#[test]
71+
fn failure() {
72+
wrapper(|env| {
73+
env.fake_release()
74+
.name("foo")
75+
.version("0.1.0")
76+
.build_result_failed()
77+
.create()?;
78+
79+
let response = env.frontend().get("/crate/foo/0.1.0/status.json").send()?;
80+
assert_cache_control(&response, CachePolicy::NoStoreMustRevalidate, &env.config());
81+
assert_eq!(response.headers()["access-control-allow-origin"], "*");
82+
let value: serde_json::Value = serde_json::from_str(&response.text()?)?;
83+
84+
assert_eq!(value, serde_json::json!({"doc_status": false}));
85+
86+
Ok(())
87+
});
88+
}
89+
90+
#[test]
91+
fn crate_version_not_found() {
92+
wrapper(|env| {
93+
env.fake_release().name("foo").version("0.1.0").create()?;
94+
95+
let response = env.frontend().get("/crate/foo/0.2.0/status.json").send()?;
96+
assert!(response
97+
.url()
98+
.as_str()
99+
.ends_with("/crate/foo/0.2.0/status.json"));
100+
assert_eq!(response.status(), StatusCode::NOT_FOUND);
101+
Ok(())
102+
});
103+
}
104+
105+
#[test]
106+
fn invalid_semver() {
107+
wrapper(|env| {
108+
env.fake_release().name("foo").version("0.1.0").create()?;
109+
110+
let response = env.frontend().get("/crate/foo/0,1,0/status.json").send()?;
111+
assert!(response
112+
.url()
113+
.as_str()
114+
.ends_with("/crate/foo/0,1,0/status.json"));
115+
assert_eq!(response.status(), StatusCode::NOT_FOUND);
116+
Ok(())
117+
});
118+
}
119+
120+
/// We only support asking for the status of exact versions
121+
#[test]
122+
fn no_semver() {
123+
wrapper(|env| {
124+
env.fake_release().name("foo").version("0.1.0").create()?;
125+
126+
let response = env.frontend().get("/crate/foo/latest/status.json").send()?;
127+
assert!(response
128+
.url()
129+
.as_str()
130+
.ends_with("/crate/foo/latest/status.json"));
131+
assert_eq!(response.status(), StatusCode::NOT_FOUND);
132+
133+
let response = env.frontend().get("/crate/foo/0.1/status.json").send()?;
134+
assert!(response
135+
.url()
136+
.as_str()
137+
.ends_with("/crate/foo/0.1/status.json"));
138+
assert_eq!(response.status(), StatusCode::NOT_FOUND);
139+
140+
Ok(())
141+
});
142+
}
143+
}

0 commit comments

Comments
 (0)