use axum::{http::StatusCode, response::IntoResponse};
use crate::persistence::files::FileIoError;
pub(crate) type HttpResult<T, E = HttpError> = core::result::Result<T, E>;
#[derive(Debug, Clone)]
pub(crate) struct HttpError {
status: StatusCode,
detail: Option<String>,
}
impl Default for HttpError {
fn default() -> Self {
Self {
status: StatusCode::INTERNAL_SERVER_ERROR,
detail: None,
}
}
}
impl HttpError {
pub fn new_with_message(status_code: StatusCode, message: impl ToString) -> HttpError {
Self {
status: status_code,
detail: Some(message.to_string()),
}
}
pub fn not_found() -> HttpError {
Self::new_with_message(StatusCode::NOT_FOUND, "Not Found")
}
pub fn internal_server() -> HttpError {
Self::new_with_message(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
}
pub fn internal_server_and_log(message: impl std::fmt::Display) -> HttpError {
tracing::error!("Internal Server Error: {}", message);
Self::internal_server()
}
pub fn bad_request(message: impl ToString) -> HttpError {
Self::new_with_message(StatusCode::BAD_REQUEST, message)
}
pub fn insufficient_storage() -> HttpError {
Self::new_with_message(
StatusCode::INSUFFICIENT_STORAGE,
"Disk space quota exceeded",
)
}
pub fn forbidden_with_message(message: impl ToString) -> HttpError {
Self::new_with_message(StatusCode::FORBIDDEN, message)
}
pub fn unauthorized() -> HttpError {
Self::new_with_message(StatusCode::UNAUTHORIZED, "Unauthorized")
}
pub fn unauthorized_with_message(message: impl ToString) -> HttpError {
Self::new_with_message(StatusCode::UNAUTHORIZED, message)
}
}
impl IntoResponse for HttpError {
fn into_response(self) -> axum::response::Response {
match self.detail {
Some(detail) => (self.status, detail).into_response(),
_ => (self.status,).into_response(),
}
}
}
impl From<std::io::Error> for HttpError {
fn from(error: std::io::Error) -> Self {
Self::internal_server_and_log(format!("IO error: {}", error))
}
}
impl From<heed::Error> for HttpError {
fn from(error: heed::Error) -> Self {
Self::internal_server_and_log(format!("LMDB error: {}", error))
}
}
impl From<anyhow::Error> for HttpError {
fn from(error: anyhow::Error) -> Self {
Self::internal_server_and_log(format!("Anyhow error: {}", error))
}
}
impl From<postcard::Error> for HttpError {
fn from(error: postcard::Error) -> Self {
Self::internal_server_and_log(format!("Postcard error: {}", error))
}
}
impl From<axum::Error> for HttpError {
fn from(error: axum::Error) -> Self {
Self::internal_server_and_log(format!("Axum error: {}", error))
}
}
impl From<axum::http::Error> for HttpError {
fn from(error: axum::http::Error) -> Self {
Self::internal_server_and_log(format!("Axum HTTP error: {}", error))
}
}
impl From<FileIoError> for HttpError {
fn from(error: FileIoError) -> Self {
match error {
FileIoError::NotFound => Self::not_found(),
FileIoError::DiskSpaceQuotaExceeded => Self::insufficient_storage(),
FileIoError::StreamBroken(_) => Self::bad_request("Stream broken"),
e => Self::internal_server_and_log(format!("FileIoError: {}", e)),
}
}
}
impl From<pubky_common::auth::Error> for HttpError {
fn from(error: pubky_common::auth::Error) -> Self {
Self::bad_request(error)
}
}