From f8543fbc8ebb6a4f9cd35bf5d0a61afa9f3794c3 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 3 Sep 2021 17:56:10 +0200 Subject: [PATCH 1/2] errors::json: Add `ServiceUnavailable` struct --- src/util/errors/json.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/util/errors/json.rs b/src/util/errors/json.rs index 81a535e932c..dab6f788589 100644 --- a/src/util/errors/json.rs +++ b/src/util/errors/json.rs @@ -80,6 +80,8 @@ pub(super) struct BadRequest(pub(super) String); #[derive(Debug)] pub(super) struct ServerError(pub(super) String); #[derive(Debug)] +pub(crate) struct ServiceUnavailable(pub(super) String); +#[derive(Debug)] pub(crate) struct TooManyRequests { pub retry_after: NaiveDateTime, } @@ -120,6 +122,18 @@ impl fmt::Display for ServerError { } } +impl AppError for ServiceUnavailable { + fn response(&self) -> Option { + Some(json_error(&self.0, StatusCode::SERVICE_UNAVAILABLE)) + } +} + +impl fmt::Display for ServiceUnavailable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + impl AppError for TooManyRequests { fn response(&self) -> Option { use std::convert::TryInto; From e1dc982c5ffa2f996b0c3a4cfcd550e1af875749 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 3 Sep 2021 17:56:43 +0200 Subject: [PATCH 2/2] errors: Cast `PoolError::UnhealthyPool` into `503 Service Unavailable` error --- src/tests/unhealthy_database.rs | 23 +++++++++++++++++++++++ src/util/errors.rs | 10 ++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/tests/unhealthy_database.rs b/src/tests/unhealthy_database.rs index 7a2b23ef89b..a056378dfe6 100644 --- a/src/tests/unhealthy_database.rs +++ b/src/tests/unhealthy_database.rs @@ -2,6 +2,7 @@ use crate::{ builders::CrateBuilder, util::{MockAnonymousUser, RequestHelper, TestApp}, }; +use http::StatusCode; use std::time::Duration; #[test] @@ -64,3 +65,25 @@ fn assert_unconditional_redirects(anon: &MockAnonymousUser) { anon.get::<()>("/api/v1/crates/awesome-project/1.0.0/download") .assert_redirect_ends_with("/awesome-project/awesome-project-1.0.0.crate"); } + +#[test] +fn http_error_with_unhealthy_database() { + let (app, anon) = TestApp::init().with_slow_real_db_pool().empty(); + + let response = anon.get::<()>("/api/v1/summary"); + assert_eq!(response.status(), StatusCode::OK); + + app.db_chaosproxy().break_networking(); + + let response = anon.get::<()>("/api/v1/summary"); + assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE); + + app.db_chaosproxy().restore_networking(); + app.as_inner() + .primary_database + .wait_until_healthy(Duration::from_millis(500)) + .expect("the database did not return healthy"); + + let response = anon.get::<()>("/api/v1/summary"); + assert_eq!(response.status(), StatusCode::OK); +} diff --git a/src/util/errors.rs b/src/util/errors.rs index ce58357c4fb..3d2cf729869 100644 --- a/src/util/errors.rs +++ b/src/util/errors.rs @@ -20,6 +20,7 @@ use std::fmt; use chrono::NaiveDateTime; use diesel::result::Error as DieselError; +use crate::db::PoolError; use crate::util::AppResponse; mod json; @@ -69,6 +70,11 @@ pub fn server_error(error: &S) -> Box { Box::new(json::ServerError(error.to_string())) } +/// Returns an error with status 503 and the provided description as JSON +pub fn service_unavailable(error: &S) -> Box { + Box::new(json::ServiceUnavailable(error.to_string())) +} + // ============================================================================= // AppError trait @@ -111,6 +117,10 @@ impl dyn AppError { } fn try_convert(err: &(dyn Error + Send + 'static)) -> Option> { + if matches!(err.downcast_ref(), Some(PoolError::UnhealthyPool)) { + return Some(service_unavailable("Service unavailable")); + } + match err.downcast_ref() { Some(DieselError::NotFound) => Some(not_found()), Some(DieselError::DatabaseError(_, info))