Skip to content

Commit 4af81bf

Browse files
committed
use axum extractor to retrieve a new database connection from the pool
1 parent f382c31 commit 4af81bf

File tree

4 files changed

+83
-45
lines changed

4 files changed

+83
-45
lines changed

src/web/error.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
use std::borrow::Cow;
2-
31
use crate::{
2+
db::PoolError,
43
storage::PathNotFoundError,
54
web::{releases::Search, AxumErrorPage},
65
};
6+
use anyhow::anyhow;
77
use axum::{
88
http::StatusCode,
99
response::{IntoResponse, Response as AxumResponse},
1010
};
11+
use std::borrow::Cow;
1112

1213
#[derive(Debug, thiserror::Error)]
1314
#[allow(dead_code)] // FIXME: remove after iron is gone
@@ -131,6 +132,12 @@ impl From<anyhow::Error> for AxumNope {
131132
}
132133
}
133134

135+
impl From<PoolError> for AxumNope {
136+
fn from(err: PoolError) -> Self {
137+
AxumNope::InternalError(anyhow!(err))
138+
}
139+
}
140+
134141
pub(crate) type AxumResult<T> = Result<T, AxumNope>;
135142

136143
#[cfg(test)]

src/web/extractors.rs

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use crate::db::{AsyncPoolClient, Pool};
2+
use anyhow::Context as _;
3+
use axum::{
4+
async_trait,
5+
extract::{Extension, FromRequestParts},
6+
http::request::Parts,
7+
RequestPartsExt,
8+
};
9+
use std::ops::{Deref, DerefMut};
10+
11+
use super::error::AxumNope;
12+
13+
/// Extractor for a async sqlx database connection.
14+
/// Can be used in normal axum handlers, middleware, or other extractors.
15+
///
16+
/// For now, we will retrieve a new connection each time the extractor is used.
17+
///
18+
/// This could be optimized in the future by caching the connection as a request
19+
/// extension, so one request only uses on connection.
20+
#[derive(Debug)]
21+
pub(crate) struct DbConnection(AsyncPoolClient);
22+
23+
#[async_trait]
24+
impl<S> FromRequestParts<S> for DbConnection
25+
where
26+
S: Send + Sync,
27+
{
28+
type Rejection = AxumNope;
29+
30+
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
31+
let Extension(pool) = parts
32+
.extract::<Extension<Pool>>()
33+
.await
34+
.context("could not extract pool extension")?;
35+
36+
Ok(Self(pool.get_async().await?))
37+
}
38+
}
39+
40+
impl Deref for DbConnection {
41+
type Target = sqlx::PgConnection;
42+
43+
fn deref(&self) -> &Self::Target {
44+
&self.0
45+
}
46+
}
47+
48+
impl DerefMut for DbConnection {
49+
fn deref_mut(&mut self) -> &mut Self::Target {
50+
&mut self.0
51+
}
52+
}
53+
54+
// TODO: we will write tests for this when async db tests are working

src/web/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub(crate) mod cache;
1515
pub(crate) mod crate_details;
1616
mod csp;
1717
pub(crate) mod error;
18+
mod extractors;
1819
mod features;
1920
mod file;
2021
mod headers;

src/web/releases.rs

+19-43
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::{
99
web::{
1010
axum_parse_uri_with_params, axum_redirect, encode_url_path,
1111
error::{AxumNope, AxumResult},
12+
extractors::DbConnection,
1213
match_version_axum,
1314
},
1415
BuildQueue, Config, InstanceMetrics,
@@ -133,7 +134,7 @@ struct SearchResult {
133134
///
134135
/// This delegates to the crates.io search API.
135136
async fn get_search_results(
136-
pool: Pool,
137+
conn: &mut sqlx::PgConnection,
137138
config: &Config,
138139
query_params: &str,
139140
) -> Result<SearchResult, anyhow::Error> {
@@ -212,10 +213,6 @@ async fn get_search_results(
212213
// So for now we are using the version with the youngest release_time.
213214
// This is different from all other release-list views where we show
214215
// our latest build.
215-
let mut conn = pool
216-
.get_async()
217-
.await
218-
.context("can't get pool connection")?;
219216
let crates: HashMap<String, Release> = sqlx::query!(
220217
r#"SELECT
221218
crates.name,
@@ -276,12 +273,7 @@ impl_axum_webpage! {
276273
HomePage = "core/home.html",
277274
}
278275

279-
pub(crate) async fn home_page(Extension(pool): Extension<Pool>) -> AxumResult<impl IntoResponse> {
280-
let mut conn = pool
281-
.get_async()
282-
.await
283-
.context("can't get pool connection")?;
284-
276+
pub(crate) async fn home_page(mut conn: DbConnection) -> AxumResult<impl IntoResponse> {
285277
let recent_releases =
286278
get_releases(&mut conn, 1, RELEASES_IN_HOME, Order::ReleaseTime, true).await?;
287279

@@ -298,14 +290,7 @@ impl_axum_webpage! {
298290
content_type = "application/xml",
299291
}
300292

301-
pub(crate) async fn releases_feed_handler(
302-
Extension(pool): Extension<Pool>,
303-
) -> AxumResult<impl IntoResponse> {
304-
let mut conn = pool
305-
.get_async()
306-
.await
307-
.context("can't get pool connection")?;
308-
293+
pub(crate) async fn releases_feed_handler(mut conn: DbConnection) -> AxumResult<impl IntoResponse> {
309294
let recent_releases =
310295
get_releases(&mut conn, 1, RELEASES_IN_FEED, Order::ReleaseTime, true).await?;
311296
Ok(ReleaseFeed { recent_releases })
@@ -337,15 +322,10 @@ pub(crate) enum ReleaseType {
337322
}
338323

339324
pub(crate) async fn releases_handler(
340-
pool: Pool,
325+
conn: &mut sqlx::PgConnection,
341326
page: Option<i64>,
342327
release_type: ReleaseType,
343328
) -> AxumResult<impl IntoResponse> {
344-
let mut conn = pool
345-
.get_async()
346-
.await
347-
.context("can't get pool connection")?;
348-
349329
let page_number = page.unwrap_or(1);
350330

351331
let (description, release_order, latest_only) = match release_type {
@@ -368,7 +348,7 @@ pub(crate) async fn releases_handler(
368348
};
369349

370350
let releases = get_releases(
371-
&mut conn,
351+
&mut *conn,
372352
page_number,
373353
RELEASES_IN_RELEASES,
374354
release_order,
@@ -395,30 +375,30 @@ pub(crate) async fn releases_handler(
395375

396376
pub(crate) async fn recent_releases_handler(
397377
page: Option<Path<i64>>,
398-
Extension(pool): Extension<Pool>,
378+
mut conn: DbConnection,
399379
) -> AxumResult<impl IntoResponse> {
400-
releases_handler(pool, page.map(|p| p.0), ReleaseType::Recent).await
380+
releases_handler(&mut conn, page.map(|p| p.0), ReleaseType::Recent).await
401381
}
402382

403383
pub(crate) async fn releases_by_stars_handler(
404384
page: Option<Path<i64>>,
405-
Extension(pool): Extension<Pool>,
385+
mut conn: DbConnection,
406386
) -> AxumResult<impl IntoResponse> {
407-
releases_handler(pool, page.map(|p| p.0), ReleaseType::Stars).await
387+
releases_handler(&mut conn, page.map(|p| p.0), ReleaseType::Stars).await
408388
}
409389

410390
pub(crate) async fn releases_recent_failures_handler(
411391
page: Option<Path<i64>>,
412-
Extension(pool): Extension<Pool>,
392+
mut conn: DbConnection,
413393
) -> AxumResult<impl IntoResponse> {
414-
releases_handler(pool, page.map(|p| p.0), ReleaseType::RecentFailures).await
394+
releases_handler(&mut conn, page.map(|p| p.0), ReleaseType::RecentFailures).await
415395
}
416396

417397
pub(crate) async fn releases_failures_by_stars_handler(
418398
page: Option<Path<i64>>,
419-
Extension(pool): Extension<Pool>,
399+
mut conn: DbConnection,
420400
) -> AxumResult<impl IntoResponse> {
421-
releases_handler(pool, page.map(|p| p.0), ReleaseType::Failures).await
401+
releases_handler(&mut conn, page.map(|p| p.0), ReleaseType::Failures).await
422402
}
423403

424404
pub(crate) async fn owner_handler(Path(owner): Path<String>) -> AxumResult<impl IntoResponse> {
@@ -460,19 +440,14 @@ impl Default for Search {
460440
async fn redirect_to_random_crate(
461441
config: Arc<Config>,
462442
metrics: Arc<InstanceMetrics>,
463-
pool: Pool,
443+
conn: &mut sqlx::PgConnection,
464444
) -> AxumResult<impl IntoResponse> {
465445
// We try to find a random crate and redirect to it.
466446
//
467447
// The query is efficient, but relies on a static factor which depends
468448
// on the amount of crates with > 100 GH stars over the amount of all crates.
469449
//
470450
// If random-crate-searches end up being empty, increase that value.
471-
let mut conn = pool
472-
.get_async()
473-
.await
474-
.context("can't get pool connection")?;
475-
476451
let row = sqlx::query!(
477452
"WITH params AS (
478453
-- get maximum possible id-value in crates-table
@@ -519,6 +494,7 @@ impl_axum_webpage! {
519494
}
520495

521496
pub(crate) async fn search_handler(
497+
mut conn: DbConnection,
522498
Extension(pool): Extension<Pool>,
523499
Extension(config): Extension<Arc<Config>>,
524500
Extension(metrics): Extension<Arc<InstanceMetrics>>,
@@ -534,7 +510,7 @@ pub(crate) async fn search_handler(
534510
if params.remove("i-am-feeling-lucky").is_some() || query.contains("::") {
535511
// redirect to a random crate if query is empty
536512
if query.is_empty() {
537-
return Ok(redirect_to_random_crate(config, metrics, pool)
513+
return Ok(redirect_to_random_crate(config, metrics, &mut conn)
538514
.await?
539515
.into_response());
540516
}
@@ -595,14 +571,14 @@ pub(crate) async fn search_handler(
595571
return Err(AxumNope::NoResults);
596572
}
597573

598-
get_search_results(pool, &config, &query_params).await?
574+
get_search_results(&mut conn, &config, &query_params).await?
599575
} else if !query.is_empty() {
600576
let query_params: String = form_urlencoded::Serializer::new(String::new())
601577
.append_pair("q", &query)
602578
.append_pair("per_page", &RELEASES_IN_RELEASES.to_string())
603579
.finish();
604580

605-
get_search_results(pool, &config, &format!("?{}", &query_params)).await?
581+
get_search_results(&mut conn, &config, &format!("?{}", &query_params)).await?
606582
} else {
607583
return Err(AxumNope::NoResults);
608584
};

0 commit comments

Comments
 (0)