Skip to main content

open_chess_server/
error.rs

1//! API errors, in Lichess's shape: `{"error": "..."}` with a status code.
2
3use axum::Json;
4use axum::http::StatusCode;
5use axum::response::{IntoResponse, Response};
6use serde_json::json;
7use tracing::warn;
8
9#[derive(Debug)]
10pub struct ApiError {
11    pub status: StatusCode,
12    pub message: String,
13}
14
15pub type ApiResult<T> = Result<T, ApiError>;
16
17impl ApiError {
18    pub fn new(status: StatusCode, message: impl Into<String>) -> ApiError {
19        ApiError { status, message: message.into() }
20    }
21
22    pub fn bad(message: impl Into<String>) -> ApiError {
23        ApiError::new(StatusCode::BAD_REQUEST, message)
24    }
25
26    pub fn not_found(what: &str) -> ApiError {
27        ApiError::new(StatusCode::NOT_FOUND, format!("No such {what}"))
28    }
29
30    pub fn unauthorized() -> ApiError {
31        ApiError::new(StatusCode::UNAUTHORIZED, "No such token")
32    }
33
34    pub fn forbidden(message: impl Into<String>) -> ApiError {
35        ApiError::new(StatusCode::FORBIDDEN, message)
36    }
37
38    pub fn missing_scope(scopes: &[&str]) -> ApiError {
39        ApiError::forbidden(format!("Missing scope: {}", scopes.join(" or ")))
40    }
41
42    pub fn too_many(message: impl Into<String>) -> ApiError {
43        ApiError::new(StatusCode::TOO_MANY_REQUESTS, message)
44    }
45
46    /// The Board/Bot API's answer to a move or action that can't happen now.
47    pub fn not_your_turn() -> ApiError {
48        ApiError::bad("Not your turn, or game already over")
49    }
50}
51
52impl From<anyhow::Error> for ApiError {
53    fn from(e: anyhow::Error) -> Self {
54        warn!("internal error: {e:#}");
55        ApiError::new(StatusCode::SERVICE_UNAVAILABLE, "temporarily unavailable, try again")
56    }
57}
58
59impl From<tokio_postgres::Error> for ApiError {
60    fn from(e: tokio_postgres::Error) -> Self {
61        ApiError::from(anyhow::Error::from(e))
62    }
63}
64
65impl IntoResponse for ApiError {
66    fn into_response(self) -> Response {
67        (self.status, Json(json!({ "error": self.message }))).into_response()
68    }
69}