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