Skip to content

Commit d40b6dd

Browse files
committed
Auto merge of #3888 - Turbo87:503, r=pietroalbini
Cast `PoolError::UnhealthyPool` into `503 Service Unavailable` error Resolves #1386 I'm not sure how to write a proper test for this unfortunately, but I've manually verified that this works as intended.
2 parents 5d2e10f + e1dc982 commit d40b6dd

File tree

3 files changed

+47
-0
lines changed

3 files changed

+47
-0
lines changed

src/tests/unhealthy_database.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::{
22
builders::CrateBuilder,
33
util::{MockAnonymousUser, RequestHelper, TestApp},
44
};
5+
use http::StatusCode;
56
use std::time::Duration;
67

78
#[test]
@@ -64,3 +65,25 @@ fn assert_unconditional_redirects(anon: &MockAnonymousUser) {
6465
anon.get::<()>("/api/v1/crates/awesome-project/1.0.0/download")
6566
.assert_redirect_ends_with("/awesome-project/awesome-project-1.0.0.crate");
6667
}
68+
69+
#[test]
70+
fn http_error_with_unhealthy_database() {
71+
let (app, anon) = TestApp::init().with_slow_real_db_pool().empty();
72+
73+
let response = anon.get::<()>("/api/v1/summary");
74+
assert_eq!(response.status(), StatusCode::OK);
75+
76+
app.db_chaosproxy().break_networking();
77+
78+
let response = anon.get::<()>("/api/v1/summary");
79+
assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
80+
81+
app.db_chaosproxy().restore_networking();
82+
app.as_inner()
83+
.primary_database
84+
.wait_until_healthy(Duration::from_millis(500))
85+
.expect("the database did not return healthy");
86+
87+
let response = anon.get::<()>("/api/v1/summary");
88+
assert_eq!(response.status(), StatusCode::OK);
89+
}

src/util/errors.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use std::fmt;
2020
use chrono::NaiveDateTime;
2121
use diesel::result::Error as DieselError;
2222

23+
use crate::db::PoolError;
2324
use crate::util::AppResponse;
2425

2526
mod json;
@@ -69,6 +70,11 @@ pub fn server_error<S: ToString + ?Sized>(error: &S) -> Box<dyn AppError> {
6970
Box::new(json::ServerError(error.to_string()))
7071
}
7172

73+
/// Returns an error with status 503 and the provided description as JSON
74+
pub fn service_unavailable<S: ToString + ?Sized>(error: &S) -> Box<dyn AppError> {
75+
Box::new(json::ServiceUnavailable(error.to_string()))
76+
}
77+
7278
// =============================================================================
7379
// AppError trait
7480

@@ -111,6 +117,10 @@ impl dyn AppError {
111117
}
112118

113119
fn try_convert(err: &(dyn Error + Send + 'static)) -> Option<Box<Self>> {
120+
if matches!(err.downcast_ref(), Some(PoolError::UnhealthyPool)) {
121+
return Some(service_unavailable("Service unavailable"));
122+
}
123+
114124
match err.downcast_ref() {
115125
Some(DieselError::NotFound) => Some(not_found()),
116126
Some(DieselError::DatabaseError(_, info))

src/util/errors/json.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ pub(super) struct BadRequest(pub(super) String);
8080
#[derive(Debug)]
8181
pub(super) struct ServerError(pub(super) String);
8282
#[derive(Debug)]
83+
pub(crate) struct ServiceUnavailable(pub(super) String);
84+
#[derive(Debug)]
8385
pub(crate) struct TooManyRequests {
8486
pub retry_after: NaiveDateTime,
8587
}
@@ -120,6 +122,18 @@ impl fmt::Display for ServerError {
120122
}
121123
}
122124

125+
impl AppError for ServiceUnavailable {
126+
fn response(&self) -> Option<AppResponse> {
127+
Some(json_error(&self.0, StatusCode::SERVICE_UNAVAILABLE))
128+
}
129+
}
130+
131+
impl fmt::Display for ServiceUnavailable {
132+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133+
self.0.fmt(f)
134+
}
135+
}
136+
123137
impl AppError for TooManyRequests {
124138
fn response(&self) -> Option<AppResponse> {
125139
use std::convert::TryInto;

0 commit comments

Comments
 (0)