1use axum::{extract::rejection::JsonRejection, response::Response, Json};
6use serde::de::DeserializeOwned;
7
8use crate::response::json_rejection_response;
9
10pub enum JsonBodyOutcome<T> {
14 Params(T),
15 Response(Response),
16}
17
18pub fn json_body_or_else<T>(
22 body: Result<Json<T>, JsonRejection>,
23 allow_missing: bool,
24 default: impl FnOnce() -> T,
25) -> JsonBodyOutcome<T>
26where
27 T: DeserializeOwned,
28{
29 match body {
30 Ok(Json(value)) => JsonBodyOutcome::Params(value),
31 Err(JsonRejection::MissingJsonContentType(_)) if allow_missing => {
32 JsonBodyOutcome::Params(default())
33 }
34 Err(error) => JsonBodyOutcome::Response(json_rejection_response(error)),
35 }
36}
37
38#[cfg(test)]
39mod tests {
40 use axum::{
41 body::Body,
42 extract::FromRequest,
43 http::{Request, StatusCode},
44 };
45 use serde_json::Value;
46
47 use super::*;
48
49 async fn extract_json(request: Request<Body>) -> Result<Json<Value>, JsonRejection> {
54 Json::<Value>::from_request(request, &()).await
55 }
56
57 #[tokio::test]
58 async fn missing_body_uses_default_when_allowed() {
59 let request = Request::builder()
60 .method("POST")
61 .uri("/")
62 .body(Body::empty())
63 .unwrap();
64 let body = extract_json(request).await;
65 match json_body_or_else(body, true, || serde_json::json!({})) {
66 JsonBodyOutcome::Params(value) => assert_eq!(value, serde_json::json!({})),
67 JsonBodyOutcome::Response(_) => panic!("expected defaulted params"),
68 }
69 }
70
71 #[tokio::test]
72 async fn missing_body_is_rejected_when_not_allowed() {
73 let request = Request::builder()
74 .method("POST")
75 .uri("/")
76 .body(Body::empty())
77 .unwrap();
78 let body = extract_json(request).await;
79 match json_body_or_else(body, false, || serde_json::json!({})) {
80 JsonBodyOutcome::Response(response) => {
81 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
82 }
83 JsonBodyOutcome::Params(_) => panic!("expected a rejection response"),
84 }
85 }
86
87 #[tokio::test]
88 async fn parsed_body_passes_through() {
89 let request = Request::builder()
90 .method("POST")
91 .uri("/")
92 .header("content-type", "application/json")
93 .body(Body::from(r#"{"a":1}"#))
94 .unwrap();
95 let body = extract_json(request).await;
96 match json_body_or_else(body, true, || serde_json::json!({})) {
97 JsonBodyOutcome::Params(value) => assert_eq!(value, serde_json::json!({"a": 1})),
98 JsonBodyOutcome::Response(_) => panic!("expected parsed params"),
99 }
100 }
101}