Skip to main content

krabby_details/
lib.rs

1//! Types to represent a problem detail error response.
2//!
3//! See [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457.html) for more details.
4use std::borrow::Cow;
5
6use bytes::{BufMut, BytesMut};
7use http::{header::CONTENT_TYPE, HeaderName, HeaderValue, StatusCode};
8
9#[derive(serde::Serialize, Debug)]
10pub struct ProblemDetails<Extension> {
11    #[serde(rename = "type")]
12    pub type_: Cow<'static, str>,
13    pub status: u16,
14    pub title: Cow<'static, str>,
15    pub detail: Cow<'static, str>,
16    #[serde(flatten)]
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub extensions: Option<Extension>,
19}
20
21#[derive(serde::Serialize)]
22pub struct ValidationErrors {
23    pub errors: Vec<ValidationError>,
24}
25
26#[derive(serde::Serialize)]
27pub struct ValidationError {
28    pub detail: String,
29    #[serde(flatten)]
30    pub source: Source,
31}
32
33/// The request part where the problem occurred.
34#[derive(serde::Serialize)]
35#[serde(tag = "source", rename_all = "snake_case")]
36pub enum Source {
37    Body {
38        /// A [JSON pointer](https://www.rfc-editor.org/info/rfc6901) targeted
39        /// at the problematic body property.
40        pointer: Option<String>,
41    },
42    Header {
43        /// The name of the problematic header.
44        name: Cow<'static, str>,
45    },
46}
47
48impl<Extension> axum_core::response::IntoResponse for ProblemDetails<Extension>
49where
50    Extension: serde::Serialize,
51{
52    fn into_response(self) -> axum_core::response::Response {
53        // Use a small initial capacity of 128 bytes like serde_json::to_vec
54        // https://docs.rs/serde_json/1.0.82/src/serde_json/ser.rs.html#2189
55        let mut buf = BytesMut::with_capacity(128).writer();
56        match serde_json::to_writer(&mut buf, &self) {
57            Ok(()) => (
58                [(CONTENT_TYPE, APPLICATION_PROBLEM_JSON)],
59                buf.into_inner().freeze(),
60            )
61                .into_response(),
62            Err(_) => INTERNAL_SERVER_ERROR.into_response(),
63        }
64    }
65}
66
67pub const APPLICATION_PROBLEM_JSON: HeaderValue =
68    HeaderValue::from_static("application/problem+json");
69
70pub const INTERNAL_SERVER_ERROR: (StatusCode, [(HeaderName, HeaderValue); 1], &[u8]) = (
71    StatusCode::INTERNAL_SERVER_ERROR,
72    [(CONTENT_TYPE, APPLICATION_PROBLEM_JSON)],
73    INTERNAL_SERVER_ERROR_PROBLEM,
74);
75
76pub const INTERNAL_SERVER_ERROR_PROBLEM: &[u8] = br#"{
77    "type": "internal_server_error",
78    "title": "Internal Server Error",
79    "detail": "Something went wrong when processing your request. Please try again later."
80    "status": 500
81}"#;