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        500 => "Internal Server Error",
21        502 => "Bad Gateway",
22        503 => "Service Unavailable",
23        504 => "Gateway Timeout",
24        _ => "Error",
25    }
26}
27
28/// Build a Problem Details value with type URI and detail.
29pub fn build_problem(status: u16, detail: impl Into<String>) -> ProblemDetails {
30    ProblemDetails {
31        typ: Some(format!("https://httpstatuses.com/{}", status)),
32        title: problem_status_title(status).into(),
33        status,
34        detail: Some(detail.into()),
35        instance: None,
36        extensions: None,
37    }
38}
39
40/// Serialize problem details to JSON bytes (for sync response setup).
41pub fn problem_to_bytes(problem: &ProblemDetails) -> Vec<u8> {
42    serde_json::to_vec(problem).unwrap_or_default()
43}
44
45/// Write RFC 7807 problem+json to the response.
46pub async fn write_problem_response(ctx: &mut dyn IHttpContext, problem: ProblemDetails) {
47    let status = problem.status;
48    ctx.response_mut().set_status(status);
49    ctx.response_mut()
50        .set_header("content-type", "application/problem+json");
51    let _ = ctx
52        .response_mut()
53        .write_bytes(problem_to_bytes(&problem))
54        .await;
55}
56
57/// Convenience: status + detail string.
58pub async fn write_problem(ctx: &mut dyn IHttpContext, status: u16, detail: impl Into<String>) {
59    write_problem_response(ctx, build_problem(status, detail)).await;
60}
61
62/// 403 with optional extension fields (e.g. `required_role`).
63pub async fn write_forbidden(
64    ctx: &mut dyn IHttpContext,
65    detail: impl Into<String>,
66    extensions: HashMap<String, serde_json::Value>,
67) {
68    let mut problem = build_problem(403, detail);
69    if !extensions.is_empty() {
70        problem.extensions = Some(extensions);
71    }
72    write_problem_response(ctx, problem).await;
73}