sova-core 0.1.2

Core HTTP primitives for Sova (App, Router, Request, Response)
Documentation
use http::Method;
use http_body_util::{BodyExt, Full};
use sova_core::extend::{Body, BoxError};
use sova_core::{App, Error, Request, Response};
use std::convert::Infallible;

#[tokio::test]
async fn query_plus_is_space() {
    let mut app = App::new();
    app.get("/q", |req: Request| async move {
        Response::text(req.query("q").unwrap_or("").to_string())
    });
    let res = app
        .handle_request(Method::GET, "/q?q=hello+world", "")
        .await;
    assert_eq!(res.body_bytes(), Some(b"hello world".as_slice()));
}

#[tokio::test]
async fn form_parse() {
    let mut app = App::new();
    app.post("/f", |mut req: Request| async move {
        #[derive(serde::Deserialize)]
        struct Form {
            user: String,
        }
        let f: Form = req.form().await.unwrap();
        Response::text(f.user)
    });
    let res = app
        .handle_request(Method::POST, "/f", "user=ada")
        .await;
    assert_eq!(res.body_bytes(), Some(b"ada".as_slice()));
}

#[tokio::test]
async fn query_as_and_param_as() {
    let mut app = App::new();
    app.get("/u/:id", |req: Request| async move {
        #[derive(serde::Deserialize)]
        struct Q {
            q: String,
        }
        let id: i64 = req.param_as("id").unwrap();
        let q: Q = req.query_as().unwrap();
        Response::text(format!("{id}:{}", q.q))
    });
    let res = app
        .handle_request(Method::GET, "/u/7?q=hi", "")
        .await;
    assert_eq!(res.body_bytes(), Some(b"7:hi".as_slice()));
}

#[tokio::test]
async fn body_consumed_names_consumer() {
    let mut req = Request::builder().body("hi").build();
    let _ = req.into_body_stream_as("multipart").unwrap();
    let err = req.json::<serde_json::Value>().await.unwrap_err();
    assert!(err.to_string().contains("multipart"), "{err}");
}

#[tokio::test]
async fn take_body_collect_bytes_and_stream() {
    let mut res = Response::text("hello");
    let bytes = res.take_body().collect().await.unwrap();
    assert_eq!(bytes.as_ref(), b"hello");

    let stream = Full::new(bytes::Bytes::from_static(b"streamed"))
        .map_err(|_: Infallible| -> BoxError { unreachable!() })
        .boxed();
    let body = Body::Stream(stream);
    let collected = body.collect().await.unwrap();
    assert_eq!(collected.as_ref(), b"streamed");
}

#[tokio::test]
async fn payload_too_large_maps_413() {
    let res = Error::PayloadTooLarge.into_response();
    assert_eq!(res.status_code().as_u16(), 413);
}

#[tokio::test]
async fn max_body_route_overrides_app() {
    use sova_core::extend::MaxBody;

    let mut app = App::new();
    app.max_body_size(10);
    app.post("/small", |mut req: Request| async move {
        let _ = req.body().await?;
        Ok::<_, Error>(Response::text("ok"))
    });
    app.post("/upload", |mut req: Request| async move {
        let b = req.body().await?;
        Ok::<_, Error>(Response::text(format!("{}", b.len())))
    })
    .with(MaxBody::bytes(100));

    let big = "x".repeat(50);
    let deny = app
        .handle(
            Request::builder()
                .method(Method::POST)
                .path("/small")
                .body(big.clone())
                .body_limit(10)
                .build(),
        )
        .await;
    assert_eq!(deny.status_code().as_u16(), 413);

    let allow = app
        .handle(
            Request::builder()
                .method(Method::POST)
                .path("/upload")
                .body(big)
                .body_limit(10)
                .build(),
        )
        .await;
    assert_eq!(allow.status_code().as_u16(), 200);
    assert_eq!(allow.body_bytes(), Some(b"50".as_slice()));
}