Skip to content

axum migration: migrate releases_handler #1920

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod html;
mod queue;
pub(crate) mod queue_builder;
mod rustc_version;
use anyhow::Result;
use anyhow::{Context as _, Result};
use postgres::Client;
use serde::de::DeserializeOwned;
use serde::Serialize;
Expand Down Expand Up @@ -78,6 +78,40 @@ where
)
}

/// a wrapper around tokio's `spawn_blocking` that
/// enables us to write nicer code when the closure
/// returns an `anyhow::Result`.
///
/// The join-error will also be converted into an `anyhow::Error`.
///
/// with standard `tokio::task::spawn_blocking`:
/// ```ignore
/// let data = spawn_blocking(move || -> anyhow::Result<_> {
/// let data = get_the_data()?;
/// Ok(data)
/// })
/// .await
/// .context("failed to join thread")??;
/// ```
///
/// with this helper function:
/// ```ignore
/// let data = spawn_blocking(move || {
/// let data = get_the_data()?;
/// Ok(data)
/// })
/// .await?
/// ```
pub(crate) async fn spawn_blocking<F, R>(f: F) -> Result<R>
where
F: FnOnce() -> Result<R> + Send + 'static,
R: Send + 'static,
{
tokio::task::spawn_blocking(f)
.await
.context("failed to join thread")?
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 2 additions & 0 deletions src/web/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ impl IntoResponse for AxumNope {
}
}

pub(crate) type AxumResult<T> = Result<T, AxumNope>;

#[cfg(test)]
mod tests {
use crate::test::wrapper;
Expand Down
108 changes: 68 additions & 40 deletions src/web/releases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,27 @@ use crate::{
build_queue::QueuedCrate,
cdn::{self, CrateInvalidation},
db::{Pool, PoolClient},
impl_webpage,
utils::report_error,
web::{error::Nope, match_version, page::WebPage, parse_url_with_params, redirect_base},
impl_axum_webpage, impl_webpage,
utils::{report_error, spawn_blocking},
web::{
error::{AxumResult, Nope},
match_version,
page::WebPage,
parse_url_with_params, redirect_base,
},
BuildQueue, Config,
};
use anyhow::{anyhow, Result};
use axum::{
extract::{Extension, Path},
response::IntoResponse,
};
use chrono::{DateTime, NaiveDate, Utc};
use iron::{
headers::{ContentType, Expires, HttpDate},
mime::{Mime, SubLevel, TopLevel},
modifiers::Redirect,
status, IronError, IronResult, Request, Response, Url,
status, IronError, IronResult, Request, Response as IronResponse, Url,
};
use postgres::Client;
use router::Router;
Expand Down Expand Up @@ -63,7 +72,7 @@ pub(crate) fn get_releases(
limit: i64,
order: Order,
latest_only: bool,
) -> Vec<Release> {
) -> Result<Vec<Release>> {
let offset = (page - 1) * limit;

// WARNING: it is _crucial_ that this always be hard-coded and NEVER be user input
Expand Down Expand Up @@ -100,8 +109,8 @@ pub(crate) fn get_releases(
}
);

conn.query(query.as_str(), &[&limit, &offset, &filter_failed])
.unwrap()
Ok(conn
.query(query.as_str(), &[&limit, &offset, &filter_failed])?
.into_iter()
.map(|row| Release {
name: row.get(0),
Expand All @@ -112,7 +121,7 @@ pub(crate) fn get_releases(
build_time: row.get(5),
stars: row.get::<_, Option<i32>>(6).unwrap_or(0),
})
.collect()
.collect())
}

struct SearchResult {
Expand Down Expand Up @@ -255,9 +264,12 @@ impl_webpage! {
HomePage = "core/home.html",
}

pub fn home_page(req: &mut Request) -> IronResult<Response> {
pub fn home_page(req: &mut Request) -> IronResult<IronResponse> {
let mut conn = extension!(req, Pool).get()?;
let recent_releases = get_releases(&mut conn, 1, RELEASES_IN_HOME, Order::ReleaseTime, true);
let recent_releases = ctry!(
req,
get_releases(&mut conn, 1, RELEASES_IN_HOME, Order::ReleaseTime, true)
);

HomePage { recent_releases }.into_response(req)
}
Expand All @@ -272,9 +284,12 @@ impl_webpage! {
content_type = ContentType(Mime(TopLevel::Application, SubLevel::Xml, vec![])),
}

pub fn releases_feed_handler(req: &mut Request) -> IronResult<Response> {
pub fn releases_feed_handler(req: &mut Request) -> IronResult<IronResponse> {
let mut conn = extension!(req, Pool).get()?;
let recent_releases = get_releases(&mut conn, 1, RELEASES_IN_FEED, Order::ReleaseTime, true);
let recent_releases = ctry!(
req,
get_releases(&mut conn, 1, RELEASES_IN_FEED, Order::ReleaseTime, true)
);

ReleaseFeed { recent_releases }.into_response(req)
}
Expand All @@ -290,25 +305,26 @@ struct ViewReleases {
owner: Option<String>,
}

impl_webpage! {
impl_axum_webpage! {
ViewReleases = "releases/releases.html",
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(super) enum ReleaseType {
pub(crate) enum ReleaseType {
Recent,
Stars,
RecentFailures,
Failures,
Search,
}

fn releases_handler(req: &mut Request, release_type: ReleaseType) -> IronResult<Response> {
let page_number: i64 = extension!(req, Router)
.find("page")
.and_then(|page_num| page_num.parse().ok())
.unwrap_or(1);
pub(crate) async fn releases_handler(
pool: Pool,
page: Option<i64>,
release_type: ReleaseType,
) -> AxumResult<impl IntoResponse> {
let page_number = page.unwrap_or(1);

let (description, release_order, latest_only) = match release_type {
ReleaseType::Recent => ("Recently uploaded crates", Order::ReleaseTime, false),
Expand All @@ -329,52 +345,64 @@ fn releases_handler(req: &mut Request, release_type: ReleaseType) -> IronResult<
}
};

let releases = {
let mut conn = extension!(req, Pool).get()?;
let releases = spawn_blocking(move || {
let mut conn = pool.get()?;
get_releases(
&mut conn,
page_number,
RELEASES_IN_RELEASES,
release_order,
latest_only,
)
};
})
.await?;

// Show next and previous page buttons
let (show_next_page, show_previous_page) = (
releases.len() == RELEASES_IN_RELEASES as usize,
page_number != 1,
);

ViewReleases {
Ok(ViewReleases {
releases,
description: description.into(),
release_type,
show_next_page,
show_previous_page,
page_number,
owner: None,
}
.into_response(req)
})
}

pub fn recent_releases_handler(req: &mut Request) -> IronResult<Response> {
releases_handler(req, ReleaseType::Recent)
pub(crate) async fn recent_releases_handler(
page: Option<Path<i64>>,
Extension(pool): Extension<Pool>,
) -> AxumResult<impl IntoResponse> {
releases_handler(pool, page.map(|p| p.0), ReleaseType::Recent).await
}

pub fn releases_by_stars_handler(req: &mut Request) -> IronResult<Response> {
releases_handler(req, ReleaseType::Stars)
pub(crate) async fn releases_by_stars_handler(
page: Option<Path<i64>>,
Extension(pool): Extension<Pool>,
) -> AxumResult<impl IntoResponse> {
releases_handler(pool, page.map(|p| p.0), ReleaseType::Stars).await
}

pub fn releases_recent_failures_handler(req: &mut Request) -> IronResult<Response> {
releases_handler(req, ReleaseType::RecentFailures)
pub(crate) async fn releases_recent_failures_handler(
page: Option<Path<i64>>,
Extension(pool): Extension<Pool>,
) -> AxumResult<impl IntoResponse> {
releases_handler(pool, page.map(|p| p.0), ReleaseType::RecentFailures).await
}

pub fn releases_failures_by_stars_handler(req: &mut Request) -> IronResult<Response> {
releases_handler(req, ReleaseType::Failures)
pub(crate) async fn releases_failures_by_stars_handler(
page: Option<Path<i64>>,
Extension(pool): Extension<Pool>,
) -> AxumResult<impl IntoResponse> {
releases_handler(pool, page.map(|p| p.0), ReleaseType::Failures).await
}

pub fn owner_handler(req: &mut Request) -> IronResult<Response> {
pub fn owner_handler(req: &mut Request) -> IronResult<IronResponse> {
let router = extension!(req, Router);
let mut owner = router.find("owner").unwrap();
if owner.starts_with('@') {
Expand Down Expand Up @@ -414,7 +442,7 @@ impl Default for Search {
}
}

fn redirect_to_random_crate(req: &Request, conn: &mut PoolClient) -> IronResult<Response> {
fn redirect_to_random_crate(req: &Request, conn: &mut PoolClient) -> IronResult<IronResponse> {
// We try to find a random crate and redirect to it.
//
// The query is efficient, but relies on a static factor which depends
Expand Down Expand Up @@ -480,7 +508,7 @@ impl_webpage! {
status = |search| search.status,
}

pub fn search_handler(req: &mut Request) -> IronResult<Response> {
pub fn search_handler(req: &mut Request) -> IronResult<IronResponse> {
let url = req.url.as_ref();
let mut params: HashMap<_, _> = url.query_pairs().collect();
let query = params
Expand Down Expand Up @@ -526,7 +554,7 @@ pub fn search_handler(req: &mut Request) -> IronResult<Response> {
ctry!(req, Url::parse(&format!("{base}/crate/{krate}/{version}")))
};

let mut resp = Response::with((status::Found, Redirect(url)));
let mut resp = IronResponse::with((status::Found, Redirect(url)));
resp.headers.set(Expires(HttpDate(time::now())));

return Ok(resp);
Expand Down Expand Up @@ -607,7 +635,7 @@ impl_webpage! {
ReleaseActivity = "releases/activity.html",
}

pub fn activity_handler(req: &mut Request) -> IronResult<Response> {
pub fn activity_handler(req: &mut Request) -> IronResult<IronResponse> {
let mut conn = extension!(req, Pool).get()?;

let data: Vec<(NaiveDate, i64, i64)> = ctry!(
Expand Down Expand Up @@ -676,7 +704,7 @@ impl_webpage! {
BuildQueuePage = "releases/build_queue.html",
}

pub fn build_queue_handler(req: &mut Request) -> IronResult<Response> {
pub fn build_queue_handler(req: &mut Request) -> IronResult<IronResponse> {
let mut queue = ctry!(req, extension!(req, BuildQueue).queued_crates());
for krate in queue.iter_mut() {
// The priority here is inverted: in the database if a crate has a higher priority it
Expand Down Expand Up @@ -730,7 +758,7 @@ mod tests {
// release without stars will not be shown
env.fake_release().name("baz").version("1.0.0").create()?;

let releases = get_releases(&mut db.conn(), 1, 10, Order::GithubStars, true);
let releases = get_releases(&mut db.conn(), 1, 10, Order::GithubStars, true).unwrap();
assert_eq!(
vec![
"bar", // 20 stars
Expand Down
61 changes: 32 additions & 29 deletions src/web/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,38 @@ pub(super) fn build_axum_routes() -> AxumRouter {
"/about/builds",
get_internal(super::sitemap::about_builds_handler),
)
.route(
"/releases",
get_internal(super::releases::recent_releases_handler),
)
.route(
"/releases/recent/:page",
get_internal(super::releases::recent_releases_handler),
)
.route(
"/releases/stars",
get_internal(super::releases::releases_by_stars_handler),
)
.route(
"/releases/stars/:page",
get_internal(super::releases::releases_by_stars_handler),
)
.route(
"/releases/recent-failures",
get_internal(super::releases::releases_recent_failures_handler),
)
.route(
"/releases/recent-failures/:page",
get_internal(super::releases::releases_recent_failures_handler),
)
.route(
"/releases/failures",
get_internal(super::releases::releases_failures_by_stars_handler),
)
.route(
"/releases/failures/:page",
get_internal(super::releases::releases_failures_by_stars_handler),
)
}

// REFACTOR: Break this into smaller initialization functions
Expand Down Expand Up @@ -105,41 +137,12 @@ pub(super) fn build_routes() -> Routes {
routes.internal_page("/about/metrics", super::metrics::metrics_handler);
routes.internal_page("/about/:subpage", super::sitemap::about_handler);

routes.internal_page("/releases", super::releases::recent_releases_handler);
routes.static_resource("/releases/feed", super::releases::releases_feed_handler);
routes.internal_page("/releases/:owner", super::releases::owner_handler);
routes.internal_page("/releases/:owner/:page", super::releases::owner_handler);
routes.internal_page("/releases/activity", super::releases::activity_handler);
routes.internal_page("/releases/search", super::releases::search_handler);
routes.internal_page("/releases/queue", super::releases::build_queue_handler);
routes.internal_page(
"/releases/recent/:page",
super::releases::recent_releases_handler,
);
routes.internal_page(
"/releases/stars",
super::releases::releases_by_stars_handler,
);
routes.internal_page(
"/releases/stars/:page",
super::releases::releases_by_stars_handler,
);
routes.internal_page(
"/releases/recent-failures",
super::releases::releases_recent_failures_handler,
);
routes.internal_page(
"/releases/recent-failures/:page",
super::releases::releases_recent_failures_handler,
);
routes.internal_page(
"/releases/failures",
super::releases::releases_failures_by_stars_handler,
);
routes.internal_page(
"/releases/failures/:page",
super::releases::releases_failures_by_stars_handler,
);

routes.internal_page("/crate/:name", super::crate_details::crate_details_handler);
routes.internal_page(
Expand Down
Loading