LCOV - code coverage report
Current view: top level - libs/utils/src/http - error.rs (source / functions) Coverage Total Hit
Test: 691a4c28fe7169edd60b367c52d448a0a6605f1f.info Lines: 0.0 % 75 0
Test Date: 2024-05-10 13:18:37 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("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            0 :     pub fn into_response(self) -> Response<Body> {
      43            0 :         match self {
      44            0 :             ApiError::BadRequest(err) => HttpErrorBody::response_from_msg_and_status(
      45            0 :                 format!("{err:#?}"), // use debug printing so that we give the cause
      46            0 :                 StatusCode::BAD_REQUEST,
      47            0 :             ),
      48              :             ApiError::Forbidden(_) => {
      49            0 :                 HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::FORBIDDEN)
      50              :             }
      51            0 :             ApiError::Unauthorized(_) => HttpErrorBody::response_from_msg_and_status(
      52            0 :                 self.to_string(),
      53            0 :                 StatusCode::UNAUTHORIZED,
      54            0 :             ),
      55              :             ApiError::NotFound(_) => {
      56            0 :                 HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::NOT_FOUND)
      57              :             }
      58              :             ApiError::Conflict(_) => {
      59            0 :                 HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::CONFLICT)
      60              :             }
      61            0 :             ApiError::PreconditionFailed(_) => HttpErrorBody::response_from_msg_and_status(
      62            0 :                 self.to_string(),
      63            0 :                 StatusCode::PRECONDITION_FAILED,
      64            0 :             ),
      65            0 :             ApiError::ShuttingDown => HttpErrorBody::response_from_msg_and_status(
      66            0 :                 "Shutting down".to_string(),
      67            0 :                 StatusCode::SERVICE_UNAVAILABLE,
      68            0 :             ),
      69            0 :             ApiError::ResourceUnavailable(err) => HttpErrorBody::response_from_msg_and_status(
      70            0 :                 err.to_string(),
      71            0 :                 StatusCode::SERVICE_UNAVAILABLE,
      72            0 :             ),
      73            0 :             ApiError::Timeout(err) => HttpErrorBody::response_from_msg_and_status(
      74            0 :                 err.to_string(),
      75            0 :                 StatusCode::REQUEST_TIMEOUT,
      76            0 :             ),
      77            0 :             ApiError::InternalServerError(err) => HttpErrorBody::response_from_msg_and_status(
      78            0 :                 err.to_string(),
      79            0 :                 StatusCode::INTERNAL_SERVER_ERROR,
      80            0 :             ),
      81              :         }
      82            0 :     }
      83              : }
      84              : 
      85            0 : #[derive(Serialize, Deserialize)]
      86              : pub struct HttpErrorBody {
      87              :     pub msg: String,
      88              : }
      89              : 
      90              : impl HttpErrorBody {
      91            0 :     pub fn from_msg(msg: String) -> Self {
      92            0 :         HttpErrorBody { msg }
      93            0 :     }
      94              : 
      95            0 :     pub fn response_from_msg_and_status(msg: String, status: StatusCode) -> Response<Body> {
      96            0 :         HttpErrorBody { msg }.to_response(status)
      97            0 :     }
      98              : 
      99            0 :     pub fn to_response(&self, status: StatusCode) -> Response<Body> {
     100            0 :         Response::builder()
     101            0 :             .status(status)
     102            0 :             .header(header::CONTENT_TYPE, "application/json")
     103            0 :             // we do not have nested maps with non string keys so serialization shouldn't fail
     104            0 :             .body(Body::from(serde_json::to_string(self).unwrap()))
     105            0 :             .unwrap()
     106            0 :     }
     107              : }
     108              : 
     109            0 : pub async fn route_error_handler(err: routerify::RouteError) -> Response<Body> {
     110            0 :     match err.downcast::<ApiError>() {
     111            0 :         Ok(api_error) => api_error_handler(*api_error),
     112            0 :         Err(other_error) => {
     113            0 :             // We expect all the request handlers to return an ApiError, so this should
     114            0 :             // 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            0 : }
     123              : 
     124            0 : pub fn api_error_handler(api_error: ApiError) -> Response<Body> {
     125            0 :     // Print a stack trace for Internal Server errors
     126            0 : 
     127            0 :     match api_error {
     128              :         ApiError::Forbidden(_) | ApiError::Unauthorized(_) => {
     129            0 :             warn!("Error processing HTTP request: {api_error:#}")
     130              :         }
     131            0 :         ApiError::ResourceUnavailable(_) => info!("Error processing HTTP request: {api_error:#}"),
     132            0 :         ApiError::NotFound(_) => info!("Error processing HTTP request: {api_error:#}"),
     133            0 :         ApiError::InternalServerError(_) => error!("Error processing HTTP request: {api_error:?}"),
     134            0 :         ApiError::ShuttingDown => info!("Shut down while processing HTTP request"),
     135            0 :         ApiError::Timeout(_) => info!("Timeout while processing HTTP request: {api_error:#}"),
     136            0 :         _ => info!("Error processing HTTP request: {api_error:#}"),
     137              :     }
     138              : 
     139            0 :     api_error.into_response()
     140            0 : }
        

Generated by: LCOV version 2.1-beta