use axum::{Json, http::StatusCode, response::IntoResponse};
use serde_json::json;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("URL error: {0}")]
Url(#[from] url::ParseError),
#[error("OAuth error: {0}")]
OAuth(String),
#[error("configuration error: {0}")]
Config(String),
#[error("upstream error: {message}")]
Upstream {
status: StatusCode,
message: String,
},
#[error("unauthorized")]
Unauthorized,
}
pub type Result<T> = std::result::Result<T, Error>;
impl Error {
pub fn oauth(message: impl Into<String>) -> Self {
Self::OAuth(message.into())
}
#[must_use]
pub fn config(message: impl Into<String>) -> Self {
Self::Config(message.into())
}
pub fn upstream(message: impl Into<String>) -> Self {
Self::Upstream {
status: StatusCode::BAD_GATEWAY,
message: message.into(),
}
}
pub fn upstream_with_status(status: StatusCode, message: impl Into<String>) -> Self {
Self::Upstream {
status,
message: message.into(),
}
}
#[must_use]
pub const fn status_code(&self) -> StatusCode {
match self {
Self::Unauthorized => StatusCode::UNAUTHORIZED,
Self::Config(_) | Self::OAuth(_) => StatusCode::BAD_REQUEST,
Self::Upstream { status, .. } => *status,
Self::Http(_) => StatusCode::BAD_GATEWAY,
Self::Io(_) | Self::Json(_) | Self::Url(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn client_message(&self) -> String {
match self {
Self::Upstream { message, .. } => message.clone(),
_ => self.to_string(),
}
}
fn error_type(&self) -> &'static str {
match self.status_code() {
StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => "authentication_error",
StatusCode::TOO_MANY_REQUESTS => "rate_limit_error",
status if status.is_server_error() || status == StatusCode::BAD_GATEWAY => {
"upstream_error"
}
_ => "invalid_request_error",
}
}
}
impl IntoResponse for Error {
fn into_response(self) -> axum::response::Response {
let status = self.status_code();
tracing::error!(status = status.as_u16(), error = %self);
let body = Json(json!({
"error": {
"message": self.client_message(),
"type": self.error_type(),
}
}));
(status, body).into_response()
}
}