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

Generated by: LCOV version 2.1-beta