LCOV - code coverage report
Current view: top level - libs/utils/src/http - error.rs (source / functions) Coverage Total Hit
Test: 20b6afc7b7f34578dcaab2b3acdaecfe91cd8bf1.info Lines: 0.0 % 84 0
Test Date: 2024-11-25 17:48:16 Functions: 0.0 % 21 0

            Line data    Source code
       1              : use hyper::{header, Body, Response, StatusCode};
       2              : use serde::{Deserialize, Serialize};
       3              : use std::borrow::Cow;
       4              : use std::error::Error as StdError;
       5              : use thiserror::Error;
       6              : use tracing::{error, info, warn};
       7              : 
       8            0 : #[derive(Debug, Error)]
       9              : pub enum ApiError {
      10              :     #[error("Bad request: {0:#?}")]
      11              :     BadRequest(anyhow::Error),
      12              : 
      13              :     #[error("Forbidden: {0}")]
      14              :     Forbidden(String),
      15              : 
      16              :     #[error("Unauthorized: {0}")]
      17              :     Unauthorized(String),
      18              : 
      19              :     #[error("NotFound: {0}")]
      20              :     NotFound(Box<dyn StdError + Send + Sync + 'static>),
      21              : 
      22              :     #[error("Conflict: {0}")]
      23              :     Conflict(String),
      24              : 
      25              :     #[error("Precondition failed: {0}")]
      26              :     PreconditionFailed(Box<str>),
      27              : 
      28              :     #[error("Resource temporarily unavailable: {0}")]
      29              :     ResourceUnavailable(Cow<'static, str>),
      30              : 
      31              :     #[error("Too many requests: {0}")]
      32              :     TooManyRequests(Cow<'static, str>),
      33              : 
      34              :     #[error("Shutting down")]
      35              :     ShuttingDown,
      36              : 
      37              :     #[error("Timeout")]
      38              :     Timeout(Cow<'static, str>),
      39              : 
      40              :     #[error("Request cancelled")]
      41              :     Cancelled,
      42              : 
      43              :     #[error(transparent)]
      44              :     InternalServerError(anyhow::Error),
      45              : }
      46              : 
      47              : impl ApiError {
      48            0 :     pub fn into_response(self) -> Response<Body> {
      49            0 :         match self {
      50            0 :             ApiError::BadRequest(err) => HttpErrorBody::response_from_msg_and_status(
      51            0 :                 format!("{err:#?}"), // use debug printing so that we give the cause
      52            0 :                 StatusCode::BAD_REQUEST,
      53            0 :             ),
      54              :             ApiError::Forbidden(_) => {
      55            0 :                 HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::FORBIDDEN)
      56              :             }
      57            0 :             ApiError::Unauthorized(_) => HttpErrorBody::response_from_msg_and_status(
      58            0 :                 self.to_string(),
      59            0 :                 StatusCode::UNAUTHORIZED,
      60            0 :             ),
      61              :             ApiError::NotFound(_) => {
      62            0 :                 HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::NOT_FOUND)
      63              :             }
      64              :             ApiError::Conflict(_) => {
      65            0 :                 HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::CONFLICT)
      66              :             }
      67            0 :             ApiError::PreconditionFailed(_) => HttpErrorBody::response_from_msg_and_status(
      68            0 :                 self.to_string(),
      69            0 :                 StatusCode::PRECONDITION_FAILED,
      70            0 :             ),
      71            0 :             ApiError::ShuttingDown => HttpErrorBody::response_from_msg_and_status(
      72            0 :                 "Shutting down".to_string(),
      73            0 :                 StatusCode::SERVICE_UNAVAILABLE,
      74            0 :             ),
      75            0 :             ApiError::ResourceUnavailable(err) => HttpErrorBody::response_from_msg_and_status(
      76            0 :                 err.to_string(),
      77            0 :                 StatusCode::SERVICE_UNAVAILABLE,
      78            0 :             ),
      79            0 :             ApiError::TooManyRequests(err) => HttpErrorBody::response_from_msg_and_status(
      80            0 :                 err.to_string(),
      81            0 :                 StatusCode::TOO_MANY_REQUESTS,
      82            0 :             ),
      83            0 :             ApiError::Timeout(err) => HttpErrorBody::response_from_msg_and_status(
      84            0 :                 err.to_string(),
      85            0 :                 StatusCode::REQUEST_TIMEOUT,
      86            0 :             ),
      87            0 :             ApiError::Cancelled => HttpErrorBody::response_from_msg_and_status(
      88            0 :                 self.to_string(),
      89            0 :                 StatusCode::INTERNAL_SERVER_ERROR,
      90            0 :             ),
      91            0 :             ApiError::InternalServerError(err) => HttpErrorBody::response_from_msg_and_status(
      92            0 :                 format!("{err:#}"), // use alternative formatting so that we give the cause without backtrace
      93            0 :                 StatusCode::INTERNAL_SERVER_ERROR,
      94            0 :             ),
      95              :         }
      96            0 :     }
      97              : }
      98              : 
      99            0 : #[derive(Serialize, Deserialize)]
     100              : pub struct HttpErrorBody {
     101              :     pub msg: String,
     102              : }
     103              : 
     104              : impl HttpErrorBody {
     105            0 :     pub fn from_msg(msg: String) -> Self {
     106            0 :         HttpErrorBody { msg }
     107            0 :     }
     108              : 
     109            0 :     pub fn response_from_msg_and_status(msg: String, status: StatusCode) -> Response<Body> {
     110            0 :         HttpErrorBody { msg }.to_response(status)
     111            0 :     }
     112              : 
     113            0 :     pub fn to_response(&self, status: StatusCode) -> Response<Body> {
     114            0 :         Response::builder()
     115            0 :             .status(status)
     116            0 :             .header(header::CONTENT_TYPE, "application/json")
     117            0 :             // we do not have nested maps with non string keys so serialization shouldn't fail
     118            0 :             .body(Body::from(serde_json::to_string(self).unwrap()))
     119            0 :             .unwrap()
     120            0 :     }
     121              : }
     122              : 
     123            0 : pub async fn route_error_handler(err: routerify::RouteError) -> Response<Body> {
     124            0 :     match err.downcast::<ApiError>() {
     125            0 :         Ok(api_error) => api_error_handler(*api_error),
     126            0 :         Err(other_error) => {
     127            0 :             // We expect all the request handlers to return an ApiError, so this should
     128            0 :             // not be reached. But just in case.
     129            0 :             error!("Error processing HTTP request: {other_error:?}");
     130            0 :             HttpErrorBody::response_from_msg_and_status(
     131            0 :                 other_error.to_string(),
     132            0 :                 StatusCode::INTERNAL_SERVER_ERROR,
     133            0 :             )
     134              :         }
     135              :     }
     136            0 : }
     137              : 
     138            0 : pub fn api_error_handler(api_error: ApiError) -> Response<Body> {
     139            0 :     // Print a stack trace for Internal Server errors
     140            0 : 
     141            0 :     match api_error {
     142              :         ApiError::Forbidden(_) | ApiError::Unauthorized(_) => {
     143            0 :             warn!("Error processing HTTP request: {api_error:#}")
     144              :         }
     145            0 :         ApiError::ResourceUnavailable(_) => info!("Error processing HTTP request: {api_error:#}"),
     146            0 :         ApiError::NotFound(_) => info!("Error processing HTTP request: {api_error:#}"),
     147            0 :         ApiError::InternalServerError(_) => error!("Error processing HTTP request: {api_error:?}"),
     148            0 :         ApiError::ShuttingDown => info!("Shut down while processing HTTP request"),
     149            0 :         ApiError::Timeout(_) => info!("Timeout while processing HTTP request: {api_error:#}"),
     150            0 :         ApiError::Cancelled => info!("Request cancelled while processing HTTP request"),
     151            0 :         _ => info!("Error processing HTTP request: {api_error:#}"),
     152              :     }
     153              : 
     154            0 :     api_error.into_response()
     155            0 : }
        

Generated by: LCOV version 2.1-beta