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 conflict(message: impl Into<String>) -> ApiError {
43 ApiError::new(StatusCode::CONFLICT, message)
44 }
45
46 pub fn too_many(message: impl Into<String>) -> ApiError {
47 ApiError::new(StatusCode::TOO_MANY_REQUESTS, message)
48 }
49
50 pub fn not_your_turn() -> ApiError {
52 ApiError::bad("Not your turn, or game already over")
53 }
54}
55
56impl From<anyhow::Error> for ApiError {
57 fn from(e: anyhow::Error) -> Self {
58 warn!("internal error: {e:#}");
59 ApiError::new(StatusCode::SERVICE_UNAVAILABLE, "temporarily unavailable, try again")
60 }
61}
62
63impl From<tokio_postgres::Error> for ApiError {
64 fn from(e: tokio_postgres::Error) -> Self {
65 ApiError::from(anyhow::Error::from(e))
66 }
67}
68
69impl IntoResponse for ApiError {
70 fn into_response(self) -> Response {
71 (self.status, Json(json!({ "error": self.message }))).into_response()
72 }
73}