open_chess_server/
error.rs1use 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 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}