mirror of
https://github.com/neondatabase/neon.git
synced 2026-05-28 02:20:42 +00:00
Part of the general work on improving pageserver logs.
Brief summary of changes:
* Remove `ApiError::from_err`
* Remove `impl From<anyhow::Error> for ApiError`
* Convert `ApiError::{BadRequest, NotFound}` to use `anyhow::Error`
* Note: `NotFound` has more verbose formatting because it's more
likely to have useful information for the receiving "user"
* Explicitly convert from `tokio::task::JoinError`s into
`InternalServerError`s where appropriate
Also note: many of the places where errors were implicitly converted to
500s have now been updated to return a more appropriate error. Some
places where it's not yet possible to distinguish the error types have
been left as 500s.
84 lines
2.6 KiB
Rust
84 lines
2.6 KiB
Rust
use hyper::{header, Body, Response, StatusCode};
|
|
use serde::{Deserialize, Serialize};
|
|
use thiserror::Error;
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum ApiError {
|
|
#[error("Bad request: {0:#?}")]
|
|
BadRequest(anyhow::Error),
|
|
|
|
#[error("Forbidden: {0}")]
|
|
Forbidden(String),
|
|
|
|
#[error("Unauthorized: {0}")]
|
|
Unauthorized(String),
|
|
|
|
#[error("NotFound: {0}")]
|
|
NotFound(anyhow::Error),
|
|
|
|
#[error("Conflict: {0}")]
|
|
Conflict(String),
|
|
|
|
#[error(transparent)]
|
|
InternalServerError(anyhow::Error),
|
|
}
|
|
|
|
impl ApiError {
|
|
pub fn into_response(self) -> Response<Body> {
|
|
match self {
|
|
ApiError::BadRequest(err) => HttpErrorBody::response_from_msg_and_status(
|
|
format!("{err:#?}"), // use debug printing so that we give the cause
|
|
StatusCode::BAD_REQUEST,
|
|
),
|
|
ApiError::Forbidden(_) => {
|
|
HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::FORBIDDEN)
|
|
}
|
|
ApiError::Unauthorized(_) => HttpErrorBody::response_from_msg_and_status(
|
|
self.to_string(),
|
|
StatusCode::UNAUTHORIZED,
|
|
),
|
|
ApiError::NotFound(_) => {
|
|
HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::NOT_FOUND)
|
|
}
|
|
ApiError::Conflict(_) => {
|
|
HttpErrorBody::response_from_msg_and_status(self.to_string(), StatusCode::CONFLICT)
|
|
}
|
|
ApiError::InternalServerError(err) => HttpErrorBody::response_from_msg_and_status(
|
|
err.to_string(),
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct HttpErrorBody {
|
|
pub msg: String,
|
|
}
|
|
|
|
impl HttpErrorBody {
|
|
pub fn from_msg(msg: String) -> Self {
|
|
HttpErrorBody { msg }
|
|
}
|
|
|
|
pub fn response_from_msg_and_status(msg: String, status: StatusCode) -> Response<Body> {
|
|
HttpErrorBody { msg }.to_response(status)
|
|
}
|
|
|
|
pub fn to_response(&self, status: StatusCode) -> Response<Body> {
|
|
Response::builder()
|
|
.status(status)
|
|
.header(header::CONTENT_TYPE, "application/json")
|
|
// we do not have nested maps with non string keys so serialization shouldn't fail
|
|
.body(Body::from(serde_json::to_string(self).unwrap()))
|
|
.unwrap()
|
|
}
|
|
}
|
|
|
|
pub async fn handler(err: routerify::RouteError) -> Response<Body> {
|
|
tracing::error!("Error processing HTTP request: {:?}", err);
|
|
err.downcast::<ApiError>()
|
|
.expect("handler should always return api error")
|
|
.into_response()
|
|
}
|