LCOV - differential code coverage report
Current view: top level - libs/utils/src/http - error.rs (source / functions) Coverage Total Hit UBC CBC
Current: cd44433dd675caa99df17a61b18949c8387e2242.info Lines: 80.3 % 71 57 14 57
Current Date: 2024-01-09 02:06:09 Functions: 61.3 % 31 19 12 19
Baseline: 66c52a629a0f4a503e193045e0df4c77139e344b.info
Baseline Date: 2024-01-08 15:34:46

           TLA  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 CBC         319 : #[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             194 :     pub fn into_response(self) -> Response<Body> {
      43             194 :         match self {
      44               3 :             ApiError::BadRequest(err) => HttpErrorBody::response_from_msg_and_status(
      45               3 :                 format!("{err:#?}"), // use debug printing so that we give the cause
      46               3 :                 StatusCode::BAD_REQUEST,
      47               3 :             ),
      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             120 :                 HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::NOT_FOUND)
      57                 :             }
      58                 :             ApiError::Conflict(_) => {
      59              17 :                 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 UBC           0 :             ApiError::ShuttingDown => HttpErrorBody::response_from_msg_and_status(
      66               0 :                 "Shutting down".to_string(),
      67               0 :                 StatusCode::SERVICE_UNAVAILABLE,
      68               0 :             ),
      69 CBC           6 :             ApiError::ResourceUnavailable(err) => HttpErrorBody::response_from_msg_and_status(
      70               6 :                 err.to_string(),
      71               6 :                 StatusCode::SERVICE_UNAVAILABLE,
      72               6 :             ),
      73 UBC           0 :             ApiError::Timeout(err) => HttpErrorBody::response_from_msg_and_status(
      74               0 :                 err.to_string(),
      75               0 :                 StatusCode::REQUEST_TIMEOUT,
      76               0 :             ),
      77 CBC          30 :             ApiError::InternalServerError(err) => HttpErrorBody::response_from_msg_and_status(
      78              30 :                 err.to_string(),
      79              30 :                 StatusCode::INTERNAL_SERVER_ERROR,
      80              30 :             ),
      81                 :         }
      82             194 :     }
      83                 : }
      84                 : 
      85             208 : #[derive(Serialize, Deserialize)]
      86                 : pub struct HttpErrorBody {
      87                 :     pub msg: String,
      88                 : }
      89                 : 
      90                 : impl HttpErrorBody {
      91              14 :     pub fn from_msg(msg: String) -> Self {
      92              14 :         HttpErrorBody { msg }
      93              14 :     }
      94                 : 
      95             194 :     pub fn response_from_msg_and_status(msg: String, status: StatusCode) -> Response<Body> {
      96             194 :         HttpErrorBody { msg }.to_response(status)
      97             194 :     }
      98                 : 
      99             194 :     pub fn to_response(&self, status: StatusCode) -> Response<Body> {
     100             194 :         Response::builder()
     101             194 :             .status(status)
     102             194 :             .header(header::CONTENT_TYPE, "application/json")
     103             194 :             // we do not have nested maps with non string keys so serialization shouldn't fail
     104             194 :             .body(Body::from(serde_json::to_string(self).unwrap()))
     105             194 :             .unwrap()
     106             194 :     }
     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 UBC           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 CBC           8 : }
     123                 : 
     124             194 : pub fn api_error_handler(api_error: ApiError) -> Response<Body> {
     125             194 :     // Print a stack trace for Internal Server errors
     126             194 : 
     127             194 :     match api_error {
     128                 :         ApiError::Forbidden(_) | ApiError::Unauthorized(_) => {
     129              14 :             warn!("Error processing HTTP request: {api_error:#}")
     130                 :         }
     131               6 :         ApiError::ResourceUnavailable(_) => info!("Error processing HTTP request: {api_error:#}"),
     132             120 :         ApiError::NotFound(_) => info!("Error processing HTTP request: {api_error:#}"),
     133              30 :         ApiError::InternalServerError(_) => error!("Error processing HTTP request: {api_error:?}"),
     134              24 :         _ => error!("Error processing HTTP request: {api_error:#}"),
     135                 :     }
     136                 : 
     137             194 :     api_error.into_response()
     138             194 : }
        

Generated by: LCOV version 2.1-beta