cortex/frontend/
catchers.rs1use rocket::http::Status;
17use rocket::request::Request;
18use rocket::response::{self, Responder};
19use rocket::serde::json::Json;
20use rocket::{Catcher, catch, catchers};
21use rocket_dyn_templates::{Template, context};
22use serde_json::json;
23
24struct NegotiatedError {
27 status: Status,
28 message: &'static str,
29}
30
31impl<'r> Responder<'r, 'static> for NegotiatedError {
32 fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> {
33 let wants_json = request.uri().path().starts_with("/api")
34 || request
35 .headers()
36 .get_one("Accept")
37 .is_some_and(|accept| accept.contains("application/json"));
38 if wants_json {
39 let body = Json(json!({ "error": self.message, "status": self.status.code }));
40 (self.status, body).respond_to(request)
41 } else {
42 let global = json!({
43 "title": format!("{} · {}", self.status.code, self.message),
44 "description": self.message,
45 });
46 let page = Template::render(
47 "error",
48 context! { global, status: self.status.code, message: self.message },
49 );
50 (self.status, page).respond_to(request)
51 }
52 }
53}
54
55#[catch(400)]
56fn bad_request(_request: &Request) -> NegotiatedError {
57 NegotiatedError {
58 status: Status::BadRequest,
59 message: "Bad request",
60 }
61}
62
63#[catch(401)]
64fn unauthorized(_request: &Request) -> NegotiatedError {
65 NegotiatedError {
66 status: Status::Unauthorized,
67 message: "Unauthorized — a valid token is required",
68 }
69}
70
71#[catch(403)]
72fn forbidden(_request: &Request) -> NegotiatedError {
73 NegotiatedError {
74 status: Status::Forbidden,
75 message: "Forbidden — this action is not permitted (e.g. a protected init/import service)",
76 }
77}
78
79#[catch(404)]
80fn not_found(_request: &Request) -> NegotiatedError {
81 NegotiatedError {
82 status: Status::NotFound,
83 message: "Not found",
84 }
85}
86
87#[catch(409)]
88fn conflict(_request: &Request) -> NegotiatedError {
89 NegotiatedError {
90 status: Status::Conflict,
91 message: "Conflict — the resource already exists, or the run is busy (e.g. tasks in progress)",
92 }
93}
94
95#[catch(422)]
96fn unprocessable(_request: &Request) -> NegotiatedError {
97 NegotiatedError {
98 status: Status::UnprocessableEntity,
99 message: "Unprocessable request — check the submitted values (e.g. an unreadable path or an \
100 unknown severity/grouping)",
101 }
102}
103
104#[catch(500)]
105fn internal_error(_request: &Request) -> NegotiatedError {
106 NegotiatedError {
107 status: Status::InternalServerError,
108 message: "Internal server error",
109 }
110}
111
112#[catch(503)]
113fn unavailable(_request: &Request) -> NegotiatedError {
114 NegotiatedError {
115 status: Status::ServiceUnavailable,
116 message: "Service unavailable — try again shortly",
117 }
118}
119
120pub fn catchers() -> Vec<Catcher> {
122 catchers![
123 bad_request,
124 unauthorized,
125 forbidden,
126 not_found,
127 conflict,
128 unprocessable,
129 internal_error,
130 unavailable
131 ]
132}