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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn walk_meta_inputs_keeps_nixpkgs_under_hyperhive_post_follows_refactor() {
|
fn walk_meta_inputs_keeps_nixpkgs_under_hyperhive_post_follows_refactor() {
|
||||||
// Reproduce the shape where meta has
|
// Reproduce the shape where meta has
|
||||||
|
|
@ -1638,8 +1655,44 @@ fn strip_container_prefix(name: &str) -> String {
|
||||||
.to_owned()
|
.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 {
|
fn error_response(message: &str) -> Response {
|
||||||
// Plain text — the JS app surfaces this in an alert(), so HTML
|
problem_response(StatusCode::INTERNAL_SERVER_ERROR, message)
|
||||||
// wrapping would just clutter the message.
|
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, message.to_owned()).into_response()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue