use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use kranz_engine::error::EngineError;
use serde::Serialize;
use serde_json::json;
use std::io::ErrorKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ApiErrorCode {
MissionNotHosted,
TurnInFlight,
RepositoryBusy,
StalePlan,
}
#[derive(Debug)]
pub struct ApiError {
pub status: StatusCode,
pub message: String,
pub code: Option<ApiErrorCode>,
}
impl ApiError {
pub fn not_found(message: impl Into<String>) -> Self {
Self {
status: StatusCode::NOT_FOUND,
message: message.into(),
code: None,
}
}
pub fn bad_request(message: impl Into<String>) -> Self {
Self {
status: StatusCode::BAD_REQUEST,
message: message.into(),
code: None,
}
}
pub fn conflict(message: impl Into<String>) -> Self {
Self {
status: StatusCode::CONFLICT,
message: message.into(),
code: None,
}
}
pub fn unauthorized(message: impl Into<String>) -> Self {
Self {
status: StatusCode::UNAUTHORIZED,
message: message.into(),
code: None,
}
}
pub fn forbidden(message: impl Into<String>) -> Self {
Self {
status: StatusCode::FORBIDDEN,
message: message.into(),
code: None,
}
}
pub fn internal(message: impl Into<String>) -> Self {
Self {
status: StatusCode::INTERNAL_SERVER_ERROR,
message: message.into(),
code: None,
}
}
pub fn unprocessable(message: impl Into<String>) -> Self {
Self {
status: StatusCode::UNPROCESSABLE_ENTITY,
message: message.into(),
code: None,
}
}
pub fn with_code(mut self, code: ApiErrorCode) -> Self {
self.code = Some(code);
self
}
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let mut body = json!({ "error": self.message });
if let Some(code) = self.code {
body["code"] = json!(code);
}
(self.status, Json(body)).into_response()
}
}
impl From<EngineError> for ApiError {
fn from(error: EngineError) -> Self {
match &error {
EngineError::Io(io) if io.kind() == ErrorKind::NotFound => {
ApiError::not_found(error.to_string())
}
EngineError::LockHeld(_) | EngineError::InvalidState(_) => {
ApiError::conflict(error.to_string())
}
EngineError::Config(_) => ApiError::bad_request(error.to_string()),
_ => ApiError::internal(error.to_string()),
}
}
}