use std::any::Any;
use axum::{Json, response::IntoResponse};
use rsketch_error::{ErrorExt, StackError, StatusCode};
use serde::Serialize;
use snafu::Snafu;
use strum::EnumProperty;
#[derive(Debug, Serialize)]
pub struct ErrorBody {
pub code: StatusCode,
pub message: String,
}
#[derive(Debug, Snafu, strum_macros::EnumProperty)]
#[snafu(visibility(pub))]
pub enum ApiError {
#[snafu(display("Invalid argument: {reason}"))]
#[strum(props(status_code = "invalid_argument"))]
InvalidArgument { reason: String },
#[snafu(display("Not found: {resource}"))]
#[strum(props(status_code = "not_found"))]
NotFound { resource: String },
#[snafu(display("Unauthorized"))]
#[strum(props(status_code = "unauthorized"))]
Unauthorized,
#[snafu(display("Forbidden"))]
#[strum(props(status_code = "forbidden"))]
Forbidden,
#[snafu(display("Conflict: {reason}"))]
#[strum(props(status_code = "conflict"))]
Conflict { reason: String },
#[snafu(display("Internal error"))]
#[strum(props(status_code = "internal"))]
Internal,
}
impl ErrorExt for ApiError {
fn status_code(&self) -> StatusCode {
self.get_str("status_code")
.and_then(|value| value.parse().ok())
.unwrap_or(StatusCode::Unknown)
}
fn as_any(&self) -> &dyn Any { self as _ }
}
impl StackError for ApiError {
fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>) {
buf.push(format!("{layer}: {self}"));
}
fn next(&self) -> Option<&dyn StackError> { None }
}
impl IntoResponse for ApiError {
fn into_response(self) -> axum::response::Response {
let body = Json(ErrorBody {
code: self.status_code(),
message: self.output_msg(),
});
(self.status_code().http_status(), body).into_response()
}
}
impl From<ApiError> for tonic::Status {
fn from(error: ApiError) -> Self {
Self::new(error.status_code().tonic_code(), error.output_msg())
}
}
pub type ApiResult<T> = std::result::Result<T, ApiError>;