Skip to main content

character_service/
error.rs

1use axum::{
2    Json,
3    http::{StatusCode, header},
4    response::{IntoResponse, Response},
5};
6use edda_types::{ActionError, Character, ValidationError};
7use serde_json::json;
8
9use crate::routes::etag;
10
11/// What a request can fail with, and the status it answers.
12#[derive(Debug, thiserror::Error)]
13pub enum ApiError {
14    #[error("character not found")]
15    NotFound,
16    #[error("{0}")]
17    BadRequest(String),
18    #[error("{0}")]
19    Forbidden(String),
20    /// Something's in the way, as a campaign's characters of an import.
21    #[error("{0}")]
22    Conflict(String),
23    #[error("an If-Match header with the character's current ETag is required")]
24    PreconditionRequired,
25    /// The client's copy is stale. Carries the current character so the
26    /// client can re-apply its change without another round trip.
27    #[error("character has been modified (current version is {})", .0.version)]
28    VersionMismatch(Box<Character>),
29    #[error(transparent)]
30    Validation(#[from] ValidationError),
31    #[error(transparent)]
32    Action(#[from] ActionError),
33    #[error("database error: {0}")]
34    Database(#[from] sqlx::Error),
35    #[error("corrupt stored data: {0}")]
36    Corrupt(String),
37}
38
39impl IntoResponse for ApiError {
40    fn into_response(self) -> Response {
41        let status = match &self {
42            ApiError::NotFound => StatusCode::NOT_FOUND,
43            ApiError::BadRequest(_) => StatusCode::BAD_REQUEST,
44            ApiError::Forbidden(_) => StatusCode::FORBIDDEN,
45            ApiError::Conflict(_) => StatusCode::CONFLICT,
46            ApiError::PreconditionRequired => StatusCode::PRECONDITION_REQUIRED,
47            ApiError::VersionMismatch(_) => StatusCode::PRECONDITION_FAILED,
48            // Out of slots/uses is a conflict with the character's current
49            // state; anything else means the request itself was wrong.
50            ApiError::Action(e) if e.is_exhausted() => StatusCode::CONFLICT,
51            ApiError::Validation(_) | ApiError::Action(_) => StatusCode::UNPROCESSABLE_ENTITY,
52            ApiError::Database(_) | ApiError::Corrupt(_) => StatusCode::INTERNAL_SERVER_ERROR,
53        };
54        let message = edda_server::error_message(status, &self);
55        match self {
56            ApiError::VersionMismatch(current) => (
57                status,
58                [(header::ETAG, etag(&current))],
59                Json(json!({ "error": message, "current": current })),
60            )
61                .into_response(),
62            _ => (status, Json(json!({ "error": message }))).into_response(),
63        }
64    }
65}