emit dashboard errors as rfc 9457 problem+json
This commit is contained in:
parent
0ec9e8b8a0
commit
b11360503a
1 changed files with 56 additions and 3 deletions
|
|
@ -1131,6 +1131,23 @@ struct RequestSpawnForm {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn problem_body_has_rfc9457_members() {
|
||||
// about:blank type → title is the canonical status reason phrase,
|
||||
// status is the numeric code, detail is the caller message.
|
||||
let body = problem_body(StatusCode::BAD_REQUEST, "bad input");
|
||||
assert_eq!(body["type"], "about:blank");
|
||||
assert_eq!(body["title"], "Bad Request");
|
||||
assert_eq!(body["status"], 400);
|
||||
assert_eq!(body["detail"], "bad input");
|
||||
// error_response (the 500 wrapper) carries the same shape with the
|
||||
// internal-error status.
|
||||
let five = problem_body(StatusCode::INTERNAL_SERVER_ERROR, "boom");
|
||||
assert_eq!(five["status"], 500);
|
||||
assert_eq!(five["title"], "Internal Server Error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk_meta_inputs_keeps_nixpkgs_under_hyperhive_post_follows_refactor() {
|
||||
// Reproduce the shape where meta has
|
||||
|
|
@ -1638,8 +1655,44 @@ fn strip_container_prefix(name: &str) -> String {
|
|||
.to_owned()
|
||||
}
|
||||
|
||||
/// The RFC 9457 problem-details media type.
|
||||
const PROBLEM_JSON_CONTENT_TYPE: &str = "application/problem+json";
|
||||
|
||||
/// Build the RFC 9457 problem-details body for `status` + `detail`. The
|
||||
/// object carries the standard members: `type` ("about:blank", i.e. no
|
||||
/// problem-specific type), `title` (the HTTP status reason phrase),
|
||||
/// `status` (numeric code) and `detail` (the caller-supplied message).
|
||||
/// Split from [`problem_response`] so the member shape is unit-testable
|
||||
/// without axum response plumbing.
|
||||
fn problem_body(status: StatusCode, detail: &str) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"type": "about:blank",
|
||||
"title": status.canonical_reason().unwrap_or("Error"),
|
||||
"status": status.as_u16(),
|
||||
"detail": detail,
|
||||
})
|
||||
}
|
||||
|
||||
/// Build an RFC 9457 (`application/problem+json`) error response.
|
||||
/// Centralising this keeps every dashboard error on one machine-readable
|
||||
/// shape the frontend can parse (read `detail` for display) instead of
|
||||
/// guessing between plain text and JSON.
|
||||
fn problem_response(status: StatusCode, detail: &str) -> Response {
|
||||
let body = serde_json::to_string(&problem_body(status, detail))
|
||||
.expect("problem+json body is always serialisable");
|
||||
(
|
||||
status,
|
||||
[(axum::http::header::CONTENT_TYPE, PROBLEM_JSON_CONTENT_TYPE)],
|
||||
body,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
/// Convenience wrapper for the common internal-error case: a 500
|
||||
/// problem-details response (see [`problem_response`]). Most dashboard
|
||||
/// handlers funnel their errors through here; handlers with a more
|
||||
/// specific failure (bad input, not found) call [`problem_response`]
|
||||
/// directly with the right status.
|
||||
fn error_response(message: &str) -> Response {
|
||||
// Plain text — the JS app surfaces this in an alert(), so HTML
|
||||
// wrapping would just clutter the message.
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, message.to_owned()).into_response()
|
||||
problem_response(StatusCode::INTERNAL_SERVER_ERROR, message)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue