Skip to main content

rust_webx_host/
problem_response.rs

1//! RFC 7807 Problem Details helpers for consistent HTTP error responses.
2
3use rust_webx_core::http::IHttpContext;
4use rust_webx_core::problem::ProblemDetails;
5use std::collections::HashMap;
6
7/// Standard HTTP status phrase for Problem Details `title`.
8pub fn problem_status_title(status: u16) -> &'static str {
9    match status {
10        400 => "Bad Request",
11        401 => "Unauthorized",
12        403 => "Forbidden",
13        404 => "Not Found",
14        405 => "Method Not Allowed",
15        409 => "Conflict",
16        413 => "Payload Too Large",
17        415 => "Unsupported Media Type",
18        422 => "Unprocessable Entity",
19        429 => "Too Many Requests",
20        501 => "Not Implemented",
21        500 => "Internal Server Error",
22        502 => "Bad Gateway",
23        503 => "Service Unavailable",
24        504 => "Gateway Timeout",
25        _ => "Error",
26    }
27}
28
29/// Build a Problem Details value with type URI and detail.
30pub fn build_problem(status: u16, detail: impl Into<String>) -> ProblemDetails {
31    ProblemDetails {
32        typ: Some(format!("https://httpstatuses.com/{}", status)),
33        title: problem_status_title(status).into(),
34        status,
35        detail: Some(detail.into()),
36        instance: None,
37        extensions: None,
38    }
39}
40
41/// Serialize problem details to JSON bytes (for sync response setup).
42pub fn problem_to_bytes(problem: &ProblemDetails) -> Vec<u8> {
43    serde_json::to_vec(problem).unwrap_or_default()
44}
45
46/// Write RFC 7807 problem+json to the response.
47pub async fn write_problem_response(ctx: &mut dyn IHttpContext, problem: ProblemDetails) {
48    let status = problem.status;
49    ctx.response_mut().set_status(status);
50    ctx.response_mut()
51        .set_header("content-type", "application/problem+json");
52    let _ = ctx
53        .response_mut()
54        .write_bytes(problem_to_bytes(&problem))
55        .await;
56}
57
58/// Convenience: status + detail string.
59pub async fn write_problem(ctx: &mut dyn IHttpContext, status: u16, detail: impl Into<String>) {
60    write_problem_response(ctx, build_problem(status, detail)).await;
61}
62
63/// 403 with optional extension fields (e.g. `required_role`).
64pub async fn write_forbidden(
65    ctx: &mut dyn IHttpContext,
66    detail: impl Into<String>,
67    extensions: HashMap<String, serde_json::Value>,
68) {
69    let mut problem = build_problem(403, detail);
70    if !extensions.is_empty() {
71        problem.extensions = Some(extensions);
72    }
73    write_problem_response(ctx, problem).await;
74}