Skip to main content

islands_actions/
error.rs

1use axum::response::{IntoResponse, Response};
2use axum::http::StatusCode;
3use axum::Json;
4use serde_json::json;
5
6/// Typed error enum for server actions.
7///
8/// `IntoResponse` maps each variant to its appropriate HTTP status + JSON body.
9/// Per project rule `use-match.md`, the status mapping uses a `match` block.
10#[derive(Debug)]
11pub enum ActionError {
12    BadRequest(String),
13    Unauthorized,
14    Forbidden,
15    NotFound,
16    Conflict(String),
17    Internal(String),
18}
19
20impl std::fmt::Display for ActionError {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        match self {
23            ActionError::BadRequest(msg) => write!(f, "bad request: {msg}"),
24            ActionError::Unauthorized => f.write_str("unauthorized"),
25            ActionError::Forbidden => f.write_str("forbidden"),
26            ActionError::NotFound => f.write_str("not found"),
27            ActionError::Conflict(msg) => write!(f, "conflict: {msg}"),
28            ActionError::Internal(msg) => write!(f, "internal error: {msg}"),
29        }
30    }
31}
32
33impl std::error::Error for ActionError {}
34
35impl IntoResponse for ActionError {
36    fn into_response(self) -> Response {
37        let (status, code, message) = match &self {
38            ActionError::BadRequest(msg) => (StatusCode::BAD_REQUEST, "BAD_REQUEST", msg.as_str()),
39            ActionError::Unauthorized => (StatusCode::UNAUTHORIZED, "UNAUTHORIZED", "unauthorized"),
40            ActionError::Forbidden => (StatusCode::FORBIDDEN, "FORBIDDEN", "forbidden"),
41            ActionError::NotFound => (StatusCode::NOT_FOUND, "NOT_FOUND", "not found"),
42            ActionError::Conflict(msg) => (StatusCode::CONFLICT, "CONFLICT", msg.as_str()),
43            ActionError::Internal(msg) => {
44                tracing::error!(error = %msg, "action internal error");
45                (StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL", "internal server error")
46            }
47        };
48        (status, Json(json!({ "error": message, "code": code }))).into_response()
49    }
50}