Skip to main content

hennery_kernel/
json.rs

1//! The JSON body extractor of every route that reads one. axum's own
2//! `Json` answers a body it refuses with plain text carrying serde's
3//! message, which quotes the request back and changes with serde. This one
4//! keeps axum's status and answers a fixed `ApiError` instead.
5
6use crate::auth_api::error;
7use axum::extract::rejection::JsonRejection;
8use axum::extract::{FromRequest, Request};
9use axum::http::StatusCode;
10use axum::response::{IntoResponse, Response};
11use serde::de::DeserializeOwned;
12
13/// `axum::Json`, with its rejections answered as `ApiError`s.
14pub struct ApiJson<T>(pub T);
15
16/// A body `ApiJson` refused, answered by its status alone.
17pub struct ApiJsonRejection(StatusCode);
18
19impl<T: DeserializeOwned, S: Send + Sync> FromRequest<S> for ApiJson<T> {
20    type Rejection = ApiJsonRejection;
21
22    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
23        match axum::Json::<T>::from_request(req, state).await {
24            Ok(axum::Json(value)) => Ok(Self(value)),
25            Err(rejection) => Err(ApiJsonRejection::from(rejection)),
26        }
27    }
28}
29
30impl From<JsonRejection> for ApiJsonRejection {
31    fn from(rejection: JsonRejection) -> Self {
32        // The status only: serde's text quotes what the client sent, which
33        // may be a secret in a field of the wrong type.
34        tracing::debug!(status = %rejection.status(), "request body refused");
35        Self(rejection.status())
36    }
37}
38
39impl IntoResponse for ApiJsonRejection {
40    fn into_response(self) -> Response {
41        let (code, message) = match self.0 {
42            StatusCode::UNSUPPORTED_MEDIA_TYPE => ("unsupported_media_type", "the body must be application/json"),
43            StatusCode::PAYLOAD_TOO_LARGE => ("body_too_large", "the request body is too large"),
44            _ => ("invalid_body", "the request body is not what this route takes"),
45        };
46        error(self.0, code, message)
47    }
48}