Line data Source code
1 : use hyper::{header, Body, Response, StatusCode};
2 : use serde::{Deserialize, Serialize};
3 : use std::error::Error as StdError;
4 : use thiserror::Error;
5 : use tracing::error;
6 :
7 423 : #[derive(Debug, Error)]
8 : pub enum ApiError {
9 : #[error("Bad request: {0:#?}")]
10 : BadRequest(anyhow::Error),
11 :
12 : #[error("Forbidden: {0}")]
13 : Forbidden(String),
14 :
15 : #[error("Unauthorized: {0}")]
16 : Unauthorized(String),
17 :
18 : #[error("NotFound: {0}")]
19 : NotFound(Box<dyn StdError + Send + Sync + 'static>),
20 :
21 : #[error("Conflict: {0}")]
22 : Conflict(String),
23 :
24 : #[error("Precondition failed: {0}")]
25 : PreconditionFailed(Box<str>),
26 :
27 : #[error(transparent)]
28 : InternalServerError(anyhow::Error),
29 : }
30 :
31 : impl ApiError {
32 258 : pub fn into_response(self) -> Response<Body> {
33 258 : match self {
34 3 : ApiError::BadRequest(err) => HttpErrorBody::response_from_msg_and_status(
35 3 : format!("{err:#?}"), // use debug printing so that we give the cause
36 3 : StatusCode::BAD_REQUEST,
37 3 : ),
38 : ApiError::Forbidden(_) => {
39 6 : HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::FORBIDDEN)
40 : }
41 5 : ApiError::Unauthorized(_) => HttpErrorBody::response_from_msg_and_status(
42 5 : self.to_string(),
43 5 : StatusCode::UNAUTHORIZED,
44 5 : ),
45 : ApiError::NotFound(_) => {
46 184 : HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::NOT_FOUND)
47 : }
48 : ApiError::Conflict(_) => {
49 11 : HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::CONFLICT)
50 : }
51 2 : ApiError::PreconditionFailed(_) => HttpErrorBody::response_from_msg_and_status(
52 2 : self.to_string(),
53 2 : StatusCode::PRECONDITION_FAILED,
54 2 : ),
55 47 : ApiError::InternalServerError(err) => HttpErrorBody::response_from_msg_and_status(
56 47 : err.to_string(),
57 47 : StatusCode::INTERNAL_SERVER_ERROR,
58 47 : ),
59 : }
60 258 : }
61 : }
62 :
63 266 : #[derive(Serialize, Deserialize)]
64 : pub struct HttpErrorBody {
65 : pub msg: String,
66 : }
67 :
68 : impl HttpErrorBody {
69 8 : pub fn from_msg(msg: String) -> Self {
70 8 : HttpErrorBody { msg }
71 8 : }
72 :
73 258 : pub fn response_from_msg_and_status(msg: String, status: StatusCode) -> Response<Body> {
74 258 : HttpErrorBody { msg }.to_response(status)
75 258 : }
76 :
77 258 : pub fn to_response(&self, status: StatusCode) -> Response<Body> {
78 258 : Response::builder()
79 258 : .status(status)
80 258 : .header(header::CONTENT_TYPE, "application/json")
81 258 : // we do not have nested maps with non string keys so serialization shouldn't fail
82 258 : .body(Body::from(serde_json::to_string(self).unwrap()))
83 258 : .unwrap()
84 258 : }
85 : }
86 :
87 5 : pub async fn route_error_handler(err: routerify::RouteError) -> Response<Body> {
88 5 : match err.downcast::<ApiError>() {
89 5 : Ok(api_error) => api_error_handler(*api_error),
90 0 : Err(other_error) => {
91 : // We expect all the request handlers to return an ApiError, so this should
92 : // not be reached. But just in case.
93 0 : error!("Error processing HTTP request: {other_error:?}");
94 0 : HttpErrorBody::response_from_msg_and_status(
95 0 : other_error.to_string(),
96 0 : StatusCode::INTERNAL_SERVER_ERROR,
97 0 : )
98 : }
99 : }
100 5 : }
101 :
102 258 : pub fn api_error_handler(api_error: ApiError) -> Response<Body> {
103 258 : // Print a stack trace for Internal Server errors
104 258 : if let ApiError::InternalServerError(_) = api_error {
105 47 : error!("Error processing HTTP request: {api_error:?}");
106 : } else {
107 211 : error!("Error processing HTTP request: {api_error:#}");
108 : }
109 :
110 258 : api_error.into_response()
111 258 : }
|