use axum::Router;
use axum::body::Body;
use axum::http::{Method, Request, StatusCode};
use axum::response::Response;
use bytes::Bytes;
use http_body_util::BodyExt;
use tower::ServiceExt;
pub async fn oneshot(router: Router, request: Request<Body>) -> Response {
router.oneshot(request).await.unwrap()
}
pub async fn oneshot_status(router: Router, uri: &str) -> StatusCode {
let req = Request::builder()
.method(Method::GET)
.uri(uri)
.body(Body::empty())
.expect("valid request");
oneshot(router, req).await.status()
}
pub async fn oneshot_status_with_method(router: Router, method: Method, uri: &str) -> StatusCode {
let req = Request::builder()
.method(method)
.uri(uri)
.body(Body::empty())
.expect("valid request");
oneshot(router, req).await.status()
}
pub async fn oneshot_body_bytes(router: Router, uri: &str) -> Bytes {
let req = Request::builder()
.method(Method::GET)
.uri(uri)
.body(Body::empty())
.expect("valid request");
let res = oneshot(router, req).await;
response_body_bytes(res).await.expect("read body")
}
pub async fn response_body_bytes(response: Response) -> Result<Bytes, std::io::Error> {
let collected = response
.into_body()
.collect()
.await
.map_err(|e| std::io::Error::other(e.to_string()))?;
Ok(collected.to_bytes())
}
#[derive(Debug)]
pub enum JsonBodyError {
Body(std::io::Error),
Json(serde_json::Error),
}
impl std::fmt::Display for JsonBodyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
JsonBodyError::Body(e) => write!(f, "body: {e}"),
JsonBodyError::Json(e) => write!(f, "json: {e}"),
}
}
}
impl std::error::Error for JsonBodyError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
JsonBodyError::Body(e) => Some(e),
JsonBodyError::Json(e) => Some(e),
}
}
}
pub async fn json_body(response: Response) -> Result<serde_json::Value, JsonBodyError> {
let bytes = response_body_bytes(response)
.await
.map_err(JsonBodyError::Body)?;
serde_json::from_slice(&bytes).map_err(JsonBodyError::Json)
}
#[cfg(test)]
mod tests {
use super::*;
use axum::routing::get;
#[tokio::test]
async fn oneshot_hits_route() {
let app = Router::new().route("/hi", get(|| async { "hello" }));
let status = oneshot_status(app, "/hi").await;
assert_eq!(status, StatusCode::OK);
}
#[tokio::test]
async fn oneshot_body_bytes_round_trip() {
let app = Router::new().route("/x", get(|| async { "payload" }));
let bytes = oneshot_body_bytes(app, "/x").await;
assert_eq!(&bytes[..], b"payload");
}
#[tokio::test]
async fn json_body_parses() {
let app = Router::new().route(
"/j",
get(|| async { axum::Json(serde_json::json!({ "a": 1 })) }),
);
let req = Request::builder().uri("/j").body(Body::empty()).unwrap();
let res = oneshot(app, req).await;
let v = json_body(res).await.expect("json");
assert_eq!(v["a"], 1);
}
}