use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use crate::types::{ErrorBody, ErrorResponse};
#[derive(Debug, Clone)]
pub struct OpenApiErrorVariant {
pub status: u16,
pub code: String,
pub message: String,
pub retryable: bool,
}
pub trait AcubeErrorInfo {
fn status_code(&self) -> StatusCode;
fn message(&self) -> &str;
fn code(&self) -> &str;
fn retryable(&self) -> bool;
fn openapi_responses() -> Vec<OpenApiErrorVariant>
where
Self: Sized,
{
Vec::new()
}
}
pub fn error_response(
status: StatusCode,
code: &str,
message: &str,
request_id: &str,
retryable: bool,
details: Option<serde_json::Value>,
) -> Response {
let body = ErrorResponse {
error: ErrorBody {
code: code.to_string(),
message: message.to_string(),
details,
request_id: request_id.to_string(),
retryable,
},
};
(status, axum::Json(body)).into_response()
}
pub fn into_acube_response(
err: &(impl AcubeErrorInfo + std::fmt::Debug),
request_id: &str,
) -> Response {
error_response(
err.status_code(),
err.code(),
err.message(),
request_id,
err.retryable(),
None,
)
}
#[derive(Debug)]
pub enum AcubeAuthError {
Forbidden(String),
}
impl IntoResponse for AcubeAuthError {
fn into_response(self) -> Response {
match self {
AcubeAuthError::Forbidden(msg) => {
let request_id = uuid::Uuid::new_v4().to_string();
error_response(
StatusCode::FORBIDDEN,
"forbidden",
&msg,
&request_id,
false,
None,
)
}
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum AcubeFrameworkError {
#[error("Not found")]
NotFound,
#[error("Method not allowed")]
MethodNotAllowed,
#[error("Payload too large")]
PayloadTooLarge,
#[error("Rate limit exceeded")]
RateLimitExceeded,
#[error("Unauthorized")]
Unauthorized,
#[error("Forbidden")]
Forbidden,
#[error("Bad request: {0}")]
BadRequest(String),
#[error("Internal server error")]
Internal,
}
impl AcubeErrorInfo for AcubeFrameworkError {
fn status_code(&self) -> StatusCode {
match self {
Self::NotFound => StatusCode::NOT_FOUND,
Self::MethodNotAllowed => StatusCode::METHOD_NOT_ALLOWED,
Self::PayloadTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
Self::RateLimitExceeded => StatusCode::TOO_MANY_REQUESTS,
Self::Unauthorized => StatusCode::UNAUTHORIZED,
Self::Forbidden => StatusCode::FORBIDDEN,
Self::BadRequest(_) => StatusCode::BAD_REQUEST,
Self::Internal => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn message(&self) -> &str {
match self {
Self::NotFound => "Not found",
Self::MethodNotAllowed => "Method not allowed",
Self::PayloadTooLarge => "Payload too large",
Self::RateLimitExceeded => "Rate limit exceeded",
Self::Unauthorized => "Unauthorized",
Self::Forbidden => "Forbidden",
Self::BadRequest(msg) => msg,
Self::Internal => "Internal server error",
}
}
fn code(&self) -> &str {
match self {
Self::NotFound => "not_found",
Self::MethodNotAllowed => "method_not_allowed",
Self::PayloadTooLarge => "payload_too_large",
Self::RateLimitExceeded => "rate_limit_exceeded",
Self::Unauthorized => "unauthorized",
Self::Forbidden => "forbidden",
Self::BadRequest(_) => "bad_request",
Self::Internal => "internal_error",
}
}
fn retryable(&self) -> bool {
matches!(self, Self::RateLimitExceeded | Self::Internal)
}
fn openapi_responses() -> Vec<OpenApiErrorVariant> {
vec![
OpenApiErrorVariant {
status: 404,
code: "not_found".to_string(),
message: "Not found".to_string(),
retryable: false,
},
OpenApiErrorVariant {
status: 405,
code: "method_not_allowed".to_string(),
message: "Method not allowed".to_string(),
retryable: false,
},
OpenApiErrorVariant {
status: 413,
code: "payload_too_large".to_string(),
message: "Payload too large".to_string(),
retryable: false,
},
OpenApiErrorVariant {
status: 429,
code: "rate_limit_exceeded".to_string(),
message: "Rate limit exceeded".to_string(),
retryable: true,
},
OpenApiErrorVariant {
status: 401,
code: "unauthorized".to_string(),
message: "Unauthorized".to_string(),
retryable: false,
},
OpenApiErrorVariant {
status: 403,
code: "forbidden".to_string(),
message: "Forbidden".to_string(),
retryable: false,
},
OpenApiErrorVariant {
status: 400,
code: "bad_request".to_string(),
message: "Bad request".to_string(),
retryable: false,
},
OpenApiErrorVariant {
status: 500,
code: "internal_error".to_string(),
message: "Internal server error".to_string(),
retryable: true,
},
]
}
}