1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
use crate::util::random_string;
use axum::{
    response::{IntoResponse, Response},
    Json,
};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use std::{
    error::Error,
    fmt::{Debug, Display},
};

#[derive(thiserror::Error, Debug, Serialize, Deserialize)]
pub struct ApiError {
    pub id: String,
    pub message: String,
}

impl Display for ApiError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

pub fn err_to_response<E: Debug>(error: E, status: StatusCode, user_message: &str) -> Response {
    let err_id = random_string();

    if status.is_server_error() {
        tracing::error!(
            ?err_id,
            ?error,
            "Server error encountered while handling request."
        );
    } else {
        tracing::warn!(
            ?err_id,
            ?error,
            "Client error encountered while handling request."
        );
    }

    let result = ApiError {
        id: err_id.clone(),
        message: user_message.to_string(),
    };

    (status, Json(result)).into_response()
}

pub trait IntoApiError<T>: Sized {
    fn or_not_found(self, user_message: &str) -> Result<T, Response> {
        self.or_status(StatusCode::NOT_FOUND, user_message)
    }

    fn or_internal_error(self, user_message: &str) -> Result<T, Response> {
        self.or_status(StatusCode::INTERNAL_SERVER_ERROR, user_message)
    }

    fn or_status(self, status: StatusCode, user_message: &str) -> Result<T, Response>;
}

impl<T, E: Error> IntoApiError<T> for Result<T, E> {
    fn or_status(self, status: StatusCode, user_message: &str) -> Result<T, Response> {
        match self {
            Ok(v) => Ok(v),
            Err(error) => {
                let err = err_to_response(&error, status, user_message);
                Err(err)
            }
        }
    }
}

impl<T> IntoApiError<T> for Option<T> {
    fn or_status(self, status: StatusCode, user_message: &str) -> Result<T, Response> {
        match self {
            Some(v) => Ok(v),
            None => {
                let err = err_to_response("Missing value.", status, user_message);
                Err(err)
            }
        }
    }
}