1use 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
13pub struct ApiJson<T>(pub T);
15
16pub 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 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}