1use axum::Router;
4use axum::body::Body;
5use axum::http::{Method, Request, StatusCode};
6use axum::response::Response;
7use bytes::Bytes;
8use http_body_util::BodyExt;
9use tower::ServiceExt;
10
11pub async fn oneshot(router: Router, request: Request<Body>) -> Response {
15 router.oneshot(request).await.unwrap()
16}
17
18pub async fn oneshot_status(router: Router, uri: &str) -> StatusCode {
20 let req = Request::builder()
21 .method(Method::GET)
22 .uri(uri)
23 .body(Body::empty())
24 .expect("valid request");
25 oneshot(router, req).await.status()
26}
27
28pub async fn oneshot_status_with_method(router: Router, method: Method, uri: &str) -> StatusCode {
30 let req = Request::builder()
31 .method(method)
32 .uri(uri)
33 .body(Body::empty())
34 .expect("valid request");
35 oneshot(router, req).await.status()
36}
37
38pub async fn oneshot_body_bytes(router: Router, uri: &str) -> Bytes {
40 let req = Request::builder()
41 .method(Method::GET)
42 .uri(uri)
43 .body(Body::empty())
44 .expect("valid request");
45 let res = oneshot(router, req).await;
46 response_body_bytes(res).await.expect("read body")
47}
48
49pub async fn response_body_bytes(response: Response) -> Result<Bytes, std::io::Error> {
51 let collected = response
52 .into_body()
53 .collect()
54 .await
55 .map_err(|e| std::io::Error::other(e.to_string()))?;
56 Ok(collected.to_bytes())
57}
58
59#[derive(Debug)]
61pub enum JsonBodyError {
62 Body(std::io::Error),
63 Json(serde_json::Error),
64}
65
66impl std::fmt::Display for JsonBodyError {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 match self {
69 JsonBodyError::Body(e) => write!(f, "body: {e}"),
70 JsonBodyError::Json(e) => write!(f, "json: {e}"),
71 }
72 }
73}
74
75impl std::error::Error for JsonBodyError {
76 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
77 match self {
78 JsonBodyError::Body(e) => Some(e),
79 JsonBodyError::Json(e) => Some(e),
80 }
81 }
82}
83
84pub async fn json_body(response: Response) -> Result<serde_json::Value, JsonBodyError> {
86 let bytes = response_body_bytes(response)
87 .await
88 .map_err(JsonBodyError::Body)?;
89 serde_json::from_slice(&bytes).map_err(JsonBodyError::Json)
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use axum::routing::get;
96
97 #[tokio::test]
98 async fn oneshot_hits_route() {
99 let app = Router::new().route("/hi", get(|| async { "hello" }));
100 let status = oneshot_status(app, "/hi").await;
101 assert_eq!(status, StatusCode::OK);
102 }
103
104 #[tokio::test]
105 async fn oneshot_body_bytes_round_trip() {
106 let app = Router::new().route("/x", get(|| async { "payload" }));
107 let bytes = oneshot_body_bytes(app, "/x").await;
108 assert_eq!(&bytes[..], b"payload");
109 }
110
111 #[tokio::test]
112 async fn json_body_parses() {
113 let app = Router::new().route(
114 "/j",
115 get(|| async { axum::Json(serde_json::json!({ "a": 1 })) }),
116 );
117 let req = Request::builder().uri("/j").body(Body::empty()).unwrap();
118 let res = oneshot(app, req).await;
119 let v = json_body(res).await.expect("json");
120 assert_eq!(v["a"], 1);
121 }
122}