1use axum::http::StatusCode;
12use axum::response::{IntoResponse, Response};
13use serde::Serialize;
14
15#[derive(Debug, Serialize)]
17struct ErrorBody {
18 error: String,
19 status: u16,
20}
21
22pub(crate) fn json_error_response(status: StatusCode, message: impl Into<String>) -> Response {
24 let body = ErrorBody {
25 error: message.into(),
26 status: status.as_u16(),
27 };
28 let json = serde_json::to_string(&body).unwrap_or_else(|_| {
29 format!(
30 r#"{{"error":"internal server error","status":{}}}"#,
31 status.as_u16()
32 )
33 });
34 (status, [("content-type", "application/json")], json).into_response()
35}
36
37pub async fn fallback_404() -> Response {
52 json_error_response(StatusCode::NOT_FOUND, "not found")
53}
54
55pub async fn fallback_405() -> Response {
70 json_error_response(StatusCode::METHOD_NOT_ALLOWED, "method not allowed")
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use http_body_util::BodyExt;
77
78 async fn response_json(resp: Response) -> serde_json::Value {
79 let bytes = resp.into_body().collect().await.unwrap().to_bytes();
80 serde_json::from_slice(&bytes).unwrap()
81 }
82
83 #[tokio::test]
84 async fn fallback_404_returns_json() {
85 let resp = fallback_404().await;
86 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
87 assert_eq!(
88 resp.headers().get("content-type").unwrap(),
89 "application/json"
90 );
91 let json = response_json(resp).await;
92 assert_eq!(json["error"], "not found");
93 assert_eq!(json["status"], 404);
94 }
95
96 #[tokio::test]
97 async fn fallback_405_returns_json() {
98 let resp = fallback_405().await;
99 assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
100 assert_eq!(
101 resp.headers().get("content-type").unwrap(),
102 "application/json"
103 );
104 let json = response_json(resp).await;
105 assert_eq!(json["error"], "method not allowed");
106 assert_eq!(json["status"], 405);
107 }
108}