Skip to content

Commit 0e1539b

Browse files
committed
migrate build-details, build-lists & crate-features to axum
1 parent 654cf6b commit 0e1539b

File tree

6 files changed

+238
-208
lines changed

6 files changed

+238
-208
lines changed

src/web/build_details.rs

Lines changed: 56 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
use crate::{
22
db::Pool,
3-
impl_webpage,
4-
web::{file::File, page::WebPage, MetaData, Nope},
3+
impl_axum_webpage,
4+
utils::spawn_blocking,
5+
web::{
6+
error::{AxumNope, AxumResult},
7+
file::File,
8+
MetaData,
9+
},
510
Config, Storage,
611
};
12+
use axum::{
13+
extract::{Extension, Path},
14+
response::IntoResponse,
15+
};
716
use chrono::{DateTime, Utc};
8-
use iron::{IronError, IronResult, Request, Response};
9-
use router::Router;
1017
use serde::Serialize;
18+
use std::sync::Arc;
1119

1220
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1321
pub(crate) struct BuildDetails {
@@ -25,66 +33,66 @@ struct BuildDetailsPage {
2533
build_details: BuildDetails,
2634
}
2735

28-
impl_webpage! {
36+
impl_axum_webpage! {
2937
BuildDetailsPage = "crate/build_details.html",
3038
}
3139

32-
pub fn build_details_handler(req: &mut Request) -> IronResult<Response> {
33-
let storage = extension!(req, Storage);
34-
let config = extension!(req, Config);
35-
let router = extension!(req, Router);
36-
let name = cexpect!(req, router.find("name"));
37-
let version = cexpect!(req, router.find("version"));
38-
let id: i32 = cexpect!(req, router.find("id"))
39-
.parse()
40-
.map_err(|_| -> IronError { Nope::BuildNotFound.into() })?;
41-
42-
let mut conn = extension!(req, Pool).get()?;
43-
44-
let row = ctry!(
45-
req,
46-
conn.query_opt(
47-
"SELECT
48-
builds.rustc_version,
49-
builds.docsrs_version,
50-
builds.build_status,
51-
builds.build_time,
52-
builds.output,
53-
releases.default_target
54-
FROM builds
55-
INNER JOIN releases ON releases.id = builds.rid
56-
INNER JOIN crates ON releases.crate_id = crates.id
57-
WHERE builds.id = $1 AND crates.name = $2 AND releases.version = $3",
58-
&[&id, &name, &version]
59-
)
60-
);
61-
62-
let build_details = if let Some(row) = row {
40+
pub(crate) async fn build_details_handler(
41+
Path((name, version, id)): Path<(String, String, String)>,
42+
Extension(pool): Extension<Pool>,
43+
Extension(config): Extension<Arc<Config>>,
44+
Extension(storage): Extension<Arc<Storage>>,
45+
) -> AxumResult<impl IntoResponse> {
46+
let id: i32 = id.parse().map_err(|_| AxumNope::BuildNotFound)?;
47+
48+
let (row, output, metadata) = spawn_blocking(move || {
49+
let mut conn = pool.get()?;
50+
let row = conn
51+
.query_opt(
52+
"SELECT
53+
builds.rustc_version,
54+
builds.docsrs_version,
55+
builds.build_status,
56+
builds.build_time,
57+
builds.output,
58+
releases.default_target
59+
FROM builds
60+
INNER JOIN releases ON releases.id = builds.rid
61+
INNER JOIN crates ON releases.crate_id = crates.id
62+
WHERE builds.id = $1 AND crates.name = $2 AND releases.version = $3",
63+
&[&id, &name, &version],
64+
)?
65+
.ok_or(AxumNope::BuildNotFound)?;
66+
6367
let output = if let Some(output) = row.get("output") {
6468
output
6569
} else {
6670
let target: String = row.get("default_target");
6771
let path = format!("build-logs/{}/{}.txt", id, target);
68-
let file = ctry!(req, File::from_path(storage, &path, config));
69-
ctry!(req, String::from_utf8(file.0.content))
72+
let file = File::from_path(&storage, &path, &config)?;
73+
String::from_utf8(file.0.content)?
7074
};
71-
BuildDetails {
75+
76+
Ok((
77+
row,
78+
output,
79+
MetaData::from_crate(&mut conn, &name, &version, &version)?,
80+
))
81+
})
82+
.await?;
83+
84+
Ok(BuildDetailsPage {
85+
metadata,
86+
build_details: BuildDetails {
7287
id,
7388
rustc_version: row.get("rustc_version"),
7489
docsrs_version: row.get("docsrs_version"),
7590
build_status: row.get("build_status"),
7691
build_time: row.get("build_time"),
7792
output,
78-
}
79-
} else {
80-
return Err(Nope::BuildNotFound.into());
81-
};
82-
83-
BuildDetailsPage {
84-
metadata: cexpect!(req, MetaData::from_crate(&mut conn, name, version, version)),
85-
build_details,
93+
},
8694
}
87-
.into_response(req)
95+
.into_response())
8896
}
8997

9098
#[cfg(test)]

src/web/builds.rs

Lines changed: 95 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1-
use super::{cache::CachePolicy, match_version, redirect_base, MatchSemver};
1+
use super::{cache::CachePolicy, MatchSemver};
22
use crate::{
33
db::Pool,
44
docbuilder::Limits,
5-
impl_webpage,
6-
web::{page::WebPage, MetaData},
5+
impl_axum_webpage,
6+
utils::spawn_blocking,
7+
web::{error::AxumResult, match_version_axum, MetaData},
78
};
8-
use chrono::{DateTime, Utc};
9-
use iron::{
10-
headers::{AccessControlAllowOrigin, ContentType},
11-
status, IronResult, Request, Response, Url,
9+
use anyhow::Result;
10+
use axum::{
11+
extract::{Extension, Path},
12+
http::header::ACCESS_CONTROL_ALLOW_ORIGIN,
13+
response::IntoResponse,
14+
Json,
1215
};
13-
use router::Router;
16+
use chrono::{DateTime, Utc};
1417
use serde::Serialize;
1518

1619
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
@@ -30,49 +33,89 @@ struct BuildsPage {
3033
canonical_url: String,
3134
}
3235

33-
impl_webpage! {
36+
impl_axum_webpage! {
3437
BuildsPage = "crate/builds.html",
3538
}
3639

37-
pub fn build_list_handler(req: &mut Request) -> IronResult<Response> {
38-
let router = extension!(req, Router);
39-
let name = cexpect!(req, router.find("name"));
40-
let req_version = router.find("version");
41-
42-
let mut conn = extension!(req, Pool).get()?;
43-
let limits = ctry!(req, Limits::for_crate(&mut conn, name));
44-
45-
let is_json = req
46-
.url
47-
.path()
48-
.last()
49-
.map_or(false, |segment| segment.ends_with(".json"));
50-
51-
let (version, version_or_latest) =
52-
match match_version(&mut conn, name, req_version).and_then(|m| m.assume_exact())? {
53-
MatchSemver::Exact((version, _)) => (version.clone(), version),
54-
MatchSemver::Latest((version, _)) => (version, "latest".to_string()),
55-
56-
MatchSemver::Semver((version, _)) => {
57-
let ext = if is_json { ".json" } else { "" };
58-
let url = ctry!(
59-
req,
60-
Url::parse(&format!(
61-
"{}/crate/{}/{}/builds{}",
62-
redirect_base(req),
63-
name,
64-
version,
65-
ext,
66-
)),
67-
);
68-
69-
return Ok(super::redirect(url));
70-
}
71-
};
72-
73-
let query = ctry!(
74-
req,
75-
conn.query(
40+
pub(crate) async fn build_list_handler(
41+
Path((name, req_version)): Path<(String, String)>,
42+
Extension(pool): Extension<Pool>,
43+
) -> AxumResult<impl IntoResponse> {
44+
let (version, version_or_latest) = match match_version_axum(&pool, &name, Some(&req_version))
45+
.await?
46+
.assume_exact()?
47+
{
48+
MatchSemver::Exact((version, _)) => (version.clone(), version),
49+
MatchSemver::Latest((version, _)) => (version, "latest".to_string()),
50+
51+
MatchSemver::Semver((version, _)) => {
52+
return Ok(super::axum_cached_redirect(
53+
&format!("/crate/{}/{}/builds", name, version),
54+
CachePolicy::ForeverInCdn,
55+
)?
56+
.into_response());
57+
}
58+
};
59+
60+
let (limits, builds, metadata) = spawn_blocking({
61+
let name = name.clone();
62+
move || {
63+
let mut conn = pool.get()?;
64+
Ok((
65+
Limits::for_crate(&mut conn, &name)?,
66+
get_builds(&mut conn, &name, &version)?,
67+
MetaData::from_crate(&mut conn, &name, &version, &version_or_latest)?,
68+
))
69+
}
70+
})
71+
.await?;
72+
73+
Ok(BuildsPage {
74+
metadata,
75+
builds,
76+
limits,
77+
canonical_url: format!("https://docs.rs/crate/{}/latest/builds", name),
78+
}
79+
.into_response())
80+
}
81+
82+
pub(crate) async fn build_list_json_handler(
83+
Path((name, req_version)): Path<(String, String)>,
84+
Extension(pool): Extension<Pool>,
85+
) -> AxumResult<impl IntoResponse> {
86+
let version = match match_version_axum(&pool, &name, Some(&req_version))
87+
.await?
88+
.assume_exact()?
89+
{
90+
MatchSemver::Exact((version, _)) | MatchSemver::Latest((version, _)) => version,
91+
MatchSemver::Semver((version, _)) => {
92+
return Ok(super::axum_cached_redirect(
93+
&format!("/crate/{}/{}/builds.json", name, version),
94+
CachePolicy::ForeverInCdn,
95+
)?
96+
.into_response());
97+
}
98+
};
99+
100+
let builds = spawn_blocking({
101+
move || {
102+
let mut conn = pool.get()?;
103+
get_builds(&mut conn, &name, &version)
104+
}
105+
})
106+
.await?;
107+
108+
Ok((
109+
Extension(CachePolicy::NoStoreMustRevalidate),
110+
[(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
111+
Json(builds),
112+
)
113+
.into_response())
114+
}
115+
116+
fn get_builds(conn: &mut postgres::Client, name: &str, version: &str) -> Result<Vec<Build>> {
117+
Ok(conn
118+
.query(
76119
"SELECT crates.name,
77120
releases.version,
78121
releases.description,
@@ -88,41 +131,17 @@ pub fn build_list_handler(req: &mut Request) -> IronResult<Response> {
88131
INNER JOIN crates ON releases.crate_id = crates.id
89132
WHERE crates.name = $1 AND releases.version = $2
90133
ORDER BY id DESC",
91-
&[&name, &version]
92-
)
93-
);
94-
95-
let builds: Vec<_> = query
96-
.into_iter()
134+
&[&name, &version],
135+
)?
136+
.iter()
97137
.map(|row| Build {
98138
id: row.get("id"),
99139
rustc_version: row.get("rustc_version"),
100140
docsrs_version: row.get("docsrs_version"),
101141
build_status: row.get("build_status"),
102142
build_time: row.get("build_time"),
103143
})
104-
.collect();
105-
106-
if is_json {
107-
let mut resp = Response::with((status::Ok, serde_json::to_string(&builds).unwrap()));
108-
resp.headers.set(ContentType::json());
109-
resp.extensions
110-
.insert::<CachePolicy>(CachePolicy::NoStoreMustRevalidate);
111-
resp.headers.set(AccessControlAllowOrigin::Any);
112-
113-
Ok(resp)
114-
} else {
115-
BuildsPage {
116-
metadata: cexpect!(
117-
req,
118-
MetaData::from_crate(&mut conn, name, &version, &version_or_latest)
119-
),
120-
builds,
121-
limits,
122-
canonical_url: format!("https://docs.rs/crate/{}/latest/builds", name),
123-
}
124-
.into_response(req)
125-
}
144+
.collect())
126145
}
127146

128147
#[cfg(test)]

0 commit comments

Comments
 (0)