LCOV - code coverage report
Current view: top level - libs/utils/src/http - error.rs (source / functions) Coverage Total Hit
Test: 32f4a56327bc9da697706839ed4836b2a00a408f.info Lines: 84.9 % 73 62
Test Date: 2024-02-07 07:37:29 Functions: 60.6 % 33 20

            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          416 : #[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("Shutting down")]
      32              :     ShuttingDown,
      33              : 
      34              :     #[error("Timeout")]
      35              :     Timeout(Cow<'static, str>),
      36              : 
      37              :     #[error(transparent)]
      38              :     InternalServerError(anyhow::Error),
      39              : }
      40              : 
      41              : impl ApiError {
      42          297 :     pub fn into_response(self) -> Response<Body> {
      43          297 :         match self {
      44           35 :             ApiError::BadRequest(err) => HttpErrorBody::response_from_msg_and_status(
      45           35 :                 format!("{err:#?}"), // use debug printing so that we give the cause
      46           35 :                 StatusCode::BAD_REQUEST,
      47           35 :             ),
      48              :             ApiError::Forbidden(_) => {
      49            9 :                 HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::FORBIDDEN)
      50              :             }
      51            5 :             ApiError::Unauthorized(_) => HttpErrorBody::response_from_msg_and_status(
      52            5 :                 self.to_string(),
      53            5 :                 StatusCode::UNAUTHORIZED,
      54            5 :             ),
      55              :             ApiError::NotFound(_) => {
      56          145 :                 HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::NOT_FOUND)
      57              :             }
      58              :             ApiError::Conflict(_) => {
      59           23 :                 HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::CONFLICT)
      60              :             }
      61            4 :             ApiError::PreconditionFailed(_) => HttpErrorBody::response_from_msg_and_status(
      62            4 :                 self.to_string(),
      63            4 :                 StatusCode::PRECONDITION_FAILED,
      64            4 :             ),
      65           29 :             ApiError::ShuttingDown => HttpErrorBody::response_from_msg_and_status(
      66           29 :                 "Shutting down".to_string(),
      67           29 :                 StatusCode::SERVICE_UNAVAILABLE,
      68           29 :             ),
      69            9 :             ApiError::ResourceUnavailable(err) => HttpErrorBody::response_from_msg_and_status(
      70            9 :                 err.to_string(),
      71            9 :                 StatusCode::SERVICE_UNAVAILABLE,
      72            9 :             ),
      73            0 :             ApiError::Timeout(err) => HttpErrorBody::response_from_msg_and_status(
      74            0 :                 err.to_string(),
      75            0 :                 StatusCode::REQUEST_TIMEOUT,
      76            0 :             ),
      77           38 :             ApiError::InternalServerError(err) => HttpErrorBody::response_from_msg_and_status(
      78           38 :                 err.to_string(),
      79           38 :                 StatusCode::INTERNAL_SERVER_ERROR,
      80           38 :             ),
      81              :         }
      82          297 :     }
      83              : }
      84              : 
      85          314 : #[derive(Serialize, Deserialize)]
      86              : pub struct HttpErrorBody {
      87              :     pub msg: String,
      88              : }
      89              : 
      90              : impl HttpErrorBody {
      91           17 :     pub fn from_msg(msg: String) -> Self {
      92           17 :         HttpErrorBody { msg }
      93           17 :     }
      94              : 
      95          297 :     pub fn response_from_msg_and_status(msg: String, status: StatusCode) -> Response<Body> {
      96          297 :         HttpErrorBody { msg }.to_response(status)
      97          297 :     }
      98              : 
      99          297 :     pub fn to_response(&self, status: StatusCode) -> Response<Body> {
     100          297 :         Response::builder()
     101          297 :             .status(status)
     102          297 :             .header(header::CONTENT_TYPE, "application/json")
     103          297 :             // we do not have nested maps with non string keys so serialization shouldn't fail
     104          297 :             .body(Body::from(serde_json::to_string(self).unwrap()))
     105          297 :             .unwrap()
     106          297 :     }
     107              : }
     108              : 
     109            8 : pub async fn route_error_handler(err: routerify::RouteError) -> Response<Body> {
     110            8 :     match err.downcast::<ApiError>() {
     111            8 :         Ok(api_error) => api_error_handler(*api_error),
     112            0 :         Err(other_error) => {
     113              :             // We expect all the request handlers to return an ApiError, so this should
     114              :             // not be reached. But just in case.
     115            0 :             error!("Error processing HTTP request: {other_error:?}");
     116            0 :             HttpErrorBody::response_from_msg_and_status(
     117            0 :                 other_error.to_string(),
     118            0 :                 StatusCode::INTERNAL_SERVER_ERROR,
     119            0 :             )
     120              :         }
     121              :     }
     122            8 : }
     123              : 
     124          297 : pub fn api_error_handler(api_error: ApiError) -> Response<Body> {
     125          297 :     // Print a stack trace for Internal Server errors
     126          297 : 
     127          297 :     match api_error {
     128              :         ApiError::Forbidden(_) | ApiError::Unauthorized(_) => {
     129           14 :             warn!("Error processing HTTP request: {api_error:#}")
     130              :         }
     131            9 :         ApiError::ResourceUnavailable(_) => info!("Error processing HTTP request: {api_error:#}"),
     132          145 :         ApiError::NotFound(_) => info!("Error processing HTTP request: {api_error:#}"),
     133           38 :         ApiError::InternalServerError(_) => error!("Error processing HTTP request: {api_error:?}"),
     134           29 :         ApiError::ShuttingDown => info!("Shut down while processing HTTP request"),
     135            0 :         ApiError::Timeout(_) => info!("Timeout while processing HTTP request: {api_error:#}"),
     136           62 :         _ => info!("Error processing HTTP request: {api_error:#}"),
     137              :     }
     138              : 
     139          297 :     api_error.into_response()
     140          297 : }
        

Generated by: LCOV version 2.1-beta