Skip to main content

combat_service/
error.rs

1use axum::{
2    http::StatusCode,
3    response::{IntoResponse, Response},
4};
5use edda_client::ClientError;
6use edda_types::combat::CombatError;
7
8/// What a request can fail with, and the status it answers.
9#[derive(Debug, thiserror::Error)]
10pub enum ApiError {
11    #[error("combat not found")]
12    NotFound,
13    #[error("{0}")]
14    BadRequest(String),
15    #[error("{0}")]
16    Forbidden(String),
17    /// Something's in the way, as a campaign's combats of an import.
18    #[error("{0}")]
19    Conflict(String),
20    /// The character's sheet refused what the command costs (no slot left).
21    #[error("{0}")]
22    Sheet(String),
23    /// The character service couldn't be reached to spend it.
24    #[error("couldn't reach the character's sheet: {0}")]
25    SheetUnavailable(String),
26    /// The monster service couldn't be reached for a stat block.
27    #[error("couldn't reach the monsters: {0}")]
28    MonstersUnavailable(String),
29    #[error(transparent)]
30    Combat(#[from] CombatError),
31    #[error("database error: {0}")]
32    Database(#[from] sqlx::Error),
33    #[error("corrupt stored data: {0}")]
34    Corrupt(String),
35}
36
37impl ApiError {
38    /// What the character service said about spending on a sheet.
39    pub fn from_sheet(error: ClientError) -> Self {
40        match error {
41            ClientError::Exhausted(message) | ClientError::Rejected(message) => {
42                ApiError::Sheet(message)
43            }
44            ClientError::NotFound => ApiError::Sheet("the character's sheet is gone".into()),
45            other => ApiError::SheetUnavailable(other.to_string()),
46        }
47    }
48
49    /// What the monster service said about a stat block.
50    pub fn from_monsters(error: ClientError) -> Self {
51        match error {
52            ClientError::NotFound => ApiError::BadRequest("there's no such monster".into()),
53            other => ApiError::MonstersUnavailable(other.to_string()),
54        }
55    }
56}
57
58impl IntoResponse for ApiError {
59    fn into_response(self) -> Response {
60        let status = match &self {
61            ApiError::NotFound => StatusCode::NOT_FOUND,
62            ApiError::BadRequest(_) => StatusCode::BAD_REQUEST,
63            ApiError::Forbidden(_) => StatusCode::FORBIDDEN,
64            ApiError::Conflict(_) => StatusCode::CONFLICT,
65            ApiError::Sheet(_) | ApiError::Combat(_) => StatusCode::UNPROCESSABLE_ENTITY,
66            ApiError::SheetUnavailable(_) | ApiError::MonstersUnavailable(_) => {
67                StatusCode::BAD_GATEWAY
68            }
69            ApiError::Database(_) | ApiError::Corrupt(_) => StatusCode::INTERNAL_SERVER_ERROR,
70        };
71        edda_server::error_response(status, &self)
72    }
73}