lightshuttle_control/
error.rs1use axum::Json;
4use axum::http::StatusCode;
5use axum::response::{IntoResponse, Response};
6use lightshuttle_runtime::LifecycleHandleError;
7use serde::Serialize;
8
9#[derive(Debug, Serialize)]
11pub struct ApiErrorBody {
12 pub error: String,
14 #[serde(skip_serializing_if = "Option::is_none")]
16 pub resource: Option<String>,
17}
18
19#[derive(Debug)]
24pub struct ApiError {
25 status: StatusCode,
26 body: ApiErrorBody,
27}
28
29impl ApiError {
30 #[must_use]
32 pub fn unknown_resource(name: impl Into<String>) -> Self {
33 Self {
34 status: StatusCode::NOT_FOUND,
35 body: ApiErrorBody {
36 error: "unknown resource".to_owned(),
37 resource: Some(name.into()),
38 },
39 }
40 }
41
42 #[must_use]
44 pub fn not_supported(op: &'static str) -> Self {
45 Self {
46 status: StatusCode::NOT_IMPLEMENTED,
47 body: ApiErrorBody {
48 error: format!("operation `{op}` is not supported yet"),
49 resource: None,
50 },
51 }
52 }
53
54 #[must_use]
56 pub fn runtime(message: impl Into<String>) -> Self {
57 Self {
58 status: StatusCode::INTERNAL_SERVER_ERROR,
59 body: ApiErrorBody {
60 error: message.into(),
61 resource: None,
62 },
63 }
64 }
65}
66
67impl From<LifecycleHandleError> for ApiError {
68 fn from(err: LifecycleHandleError) -> Self {
69 match err {
70 LifecycleHandleError::UnknownResource(name) => Self::unknown_resource(name),
71 LifecycleHandleError::NotSupported(op) => Self::not_supported(op),
72 LifecycleHandleError::Runtime(e) => Self::runtime(e.to_string()),
73 }
74 }
75}
76
77impl IntoResponse for ApiError {
78 fn into_response(self) -> Response {
79 (self.status, Json(self.body)).into_response()
80 }
81}