use axum::Json;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use lightshuttle_runtime::LifecycleHandleError;
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct ApiErrorBody {
pub error: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub resource: Option<String>,
}
#[derive(Debug)]
pub struct ApiError {
status: StatusCode,
body: ApiErrorBody,
}
impl ApiError {
#[must_use]
pub fn unknown_resource(name: impl Into<String>) -> Self {
Self {
status: StatusCode::NOT_FOUND,
body: ApiErrorBody {
error: "unknown resource".to_owned(),
resource: Some(name.into()),
},
}
}
#[must_use]
pub fn not_supported(op: &'static str) -> Self {
Self {
status: StatusCode::NOT_IMPLEMENTED,
body: ApiErrorBody {
error: format!("operation `{op}` is not supported yet"),
resource: None,
},
}
}
#[must_use]
pub fn runtime(message: impl Into<String>) -> Self {
Self {
status: StatusCode::INTERNAL_SERVER_ERROR,
body: ApiErrorBody {
error: message.into(),
resource: None,
},
}
}
}
impl From<LifecycleHandleError> for ApiError {
fn from(err: LifecycleHandleError) -> Self {
match err {
LifecycleHandleError::UnknownResource(name) => Self::unknown_resource(name),
LifecycleHandleError::NotSupported(op) => Self::not_supported(op),
LifecycleHandleError::Runtime(e) => Self::runtime(e.to_string()),
}
}
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
(self.status, Json(self.body)).into_response()
}
}