plane/controller/
error.rs

1use axum::{
2    http::StatusCode,
3    response::{IntoResponse, Response},
4    Json,
5};
6use plane_common::{
7    protocol::{ApiError, ApiErrorKind},
8    util::random_string,
9};
10use std::{error::Error, fmt::Debug};
11
12pub fn err_to_response<E: Debug>(
13    error: E,
14    status: StatusCode,
15    user_message: &str,
16    code: ApiErrorKind,
17) -> Response {
18    let err_id = random_string();
19
20    if status.is_server_error() {
21        tracing::error!(
22            ?err_id,
23            ?error,
24            "Server error encountered while handling request."
25        );
26    } else {
27        tracing::warn!(
28            ?err_id,
29            ?error,
30            "Client error encountered while handling request."
31        );
32    }
33
34    let result = ApiError {
35        id: err_id.clone(),
36        message: user_message.to_string(),
37        kind: code,
38    };
39
40    (status, Json(result)).into_response()
41}
42
43pub trait IntoApiError<T>: Sized {
44    fn or_not_found(self, user_message: &str) -> Result<T, Response> {
45        self.or_status(StatusCode::NOT_FOUND, user_message, ApiErrorKind::NotFound)
46    }
47
48    fn or_internal_error(self, user_message: &str) -> Result<T, Response> {
49        self.or_status(
50            StatusCode::INTERNAL_SERVER_ERROR,
51            user_message,
52            ApiErrorKind::Other,
53        )
54    }
55
56    fn or_status(
57        self,
58        status: StatusCode,
59        user_message: &str,
60        code: ApiErrorKind,
61    ) -> Result<T, Response>;
62}
63
64impl<T, E: Error> IntoApiError<T> for Result<T, E> {
65    fn or_status(
66        self,
67        status: StatusCode,
68        user_message: &str,
69        code: ApiErrorKind,
70    ) -> Result<T, Response> {
71        match self {
72            Ok(v) => Ok(v),
73            Err(error) => {
74                let err = err_to_response(&error, status, user_message, code);
75                Err(err)
76            }
77        }
78    }
79}
80
81impl<T> IntoApiError<T> for Option<T> {
82    fn or_status(
83        self,
84        status: StatusCode,
85        user_message: &str,
86        code: ApiErrorKind,
87    ) -> Result<T, Response> {
88        match self {
89            Some(v) => Ok(v),
90            None => {
91                let err = err_to_response("Missing value.", status, user_message, code);
92                Err(err)
93            }
94        }
95    }
96}