From e1dc982c5ffa2f996b0c3a4cfcd550e1af875749 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 3 Sep 2021 17:56:43 +0200 Subject: [PATCH] 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 7a2b23ef89..a056378dfe 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 ce58357c4f..3d2cf72986 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))