Skip to main content

mj_controller/server/api/
failure.rs

1use super::*;
2
3/// An API failure with a message written for the caller.
4///
5/// The phone surface deliberately answers with fixed strings, because its
6/// errors would otherwise name profile homes and SSH hosts to a browser. Here
7/// the caller is the same user who owns the daemon, and the whole value of the
8/// API is knowing *why* a turn or an export failed, so the message is dynamic.
9#[derive(Debug)]
10pub struct ApiFailure {
11    pub status: StatusCode,
12    pub message: String,
13}
14
15impl ApiFailure {
16    pub fn new(status: StatusCode, message: impl Into<String>) -> Self {
17        Self {
18            status,
19            message: message.into(),
20        }
21    }
22
23    pub fn bad_request(message: impl Into<String>) -> Self {
24        Self::new(StatusCode::BAD_REQUEST, message)
25    }
26
27    pub fn conflict(message: impl Into<String>) -> Self {
28        Self::new(StatusCode::CONFLICT, message)
29    }
30
31    pub fn not_found(message: impl Into<String>) -> Self {
32        Self::new(StatusCode::NOT_FOUND, message)
33    }
34
35    pub fn unavailable(message: impl Into<String>) -> Self {
36        Self::new(StatusCode::SERVICE_UNAVAILABLE, message)
37    }
38}
39
40impl std::fmt::Display for ApiFailure {
41    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        write!(formatter, "{}: {}", self.status, self.message)
43    }
44}
45
46impl From<ApiError> for ApiFailure {
47    fn from(error: ApiError) -> Self {
48        Self::new(error.status, error.message)
49    }
50}
51
52impl From<anyhow::Error> for ApiFailure {
53    fn from(error: anyhow::Error) -> Self {
54        if let Some(refusal) = mj_core::refusal::Refusal::of(&error) {
55            return match refusal.kind() {
56                mj_core::refusal::RefusalKind::Precondition => Self::conflict(refusal.message()),
57                mj_core::refusal::RefusalKind::Unusable => Self::bad_request(refusal.message()),
58            };
59        }
60        Self::new(StatusCode::INTERNAL_SERVER_ERROR, format!("{error:#}"))
61    }
62}
63
64#[derive(Debug, Serialize)]
65pub(super) struct FailureBody {
66    pub(super) error: String,
67}
68
69impl IntoResponse for ApiFailure {
70    fn into_response(self) -> Response {
71        (
72            self.status,
73            Json(FailureBody {
74                error: self.message,
75            }),
76        )
77            .into_response()
78    }
79}
80
81// ---------------------------------------------------------------------------
82// Wire types
83// ---------------------------------------------------------------------------