mj_controller/server/api/
failure.rs1use super::*;
2
3#[derive(Debug)]
10pub struct ApiFailure {
11 pub status: StatusCode,
12 pub message: String,
13 pub code: Option<&'static str>,
15 busy: Option<(usize, usize)>,
18}
19
20impl ApiFailure {
21 pub fn new(status: StatusCode, message: impl Into<String>) -> Self {
22 Self {
23 status,
24 message: message.into(),
25 code: None,
26 busy: None,
27 }
28 }
29
30 #[must_use]
31 pub fn with_code(mut self, code: Option<&'static str>) -> Self {
32 self.code = code;
33 self
34 }
35
36 pub fn bad_request(message: impl Into<String>) -> Self {
37 Self::new(StatusCode::BAD_REQUEST, message)
38 }
39
40 pub fn conflict(message: impl Into<String>) -> Self {
41 Self::new(StatusCode::CONFLICT, message)
42 }
43
44 pub fn not_found(message: impl Into<String>) -> Self {
45 Self::new(StatusCode::NOT_FOUND, message)
46 }
47
48 pub fn unavailable(message: impl Into<String>) -> Self {
49 Self::new(StatusCode::SERVICE_UNAVAILABLE, message)
50 }
51}
52
53impl std::fmt::Display for ApiFailure {
54 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 write!(formatter, "{}: {}", self.status, self.message)
56 }
57}
58
59impl From<ApiError> for ApiFailure {
60 fn from(error: ApiError) -> Self {
61 let mut failure = Self::new(error.status, error.message).with_code(error.code);
62 failure.busy = error.busy;
63 failure
64 }
65}
66
67impl From<anyhow::Error> for ApiFailure {
68 fn from(error: anyhow::Error) -> Self {
69 if let Some(refusal) = mj_core::refusal::Refusal::of(&error) {
70 return match refusal.kind() {
71 mj_core::refusal::RefusalKind::Precondition => Self::conflict(refusal.message()),
72 mj_core::refusal::RefusalKind::Unusable => Self::bad_request(refusal.message()),
73 }
74 .with_code(refusal.code());
75 }
76 Self::new(StatusCode::INTERNAL_SERVER_ERROR, format!("{error:#}"))
77 }
78}
79
80#[derive(Debug, Serialize)]
81pub(super) struct FailureBody {
82 pub(super) error: String,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub(super) code: Option<&'static str>,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub(super) running_actions: Option<usize>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub(super) action_limit: Option<usize>,
89}
90
91impl IntoResponse for ApiFailure {
92 fn into_response(self) -> Response {
93 (
94 self.status,
95 Json(FailureBody {
96 error: self.message,
97 code: self.code,
98 running_actions: self.busy.map(|(running, _)| running),
99 action_limit: self.busy.map(|(_, limit)| limit),
100 }),
101 )
102 .into_response()
103 }
104}
105
106