client-util 0.3.0

Help you to build requests and handle responses by several extension trait!
Documentation
use client_util::prelude::*;
use http::StatusCode;
use http_body_util::BodyExt;
mod support;

#[tokio::test]
async fn text_part() -> client_util::Result<()> {
    let form = Form::new().text("foo", "bar");

    let expected_body = format!(
        "\
         --{0}\r\n\
         Content-Disposition: form-data; name=\"foo\"\r\n\r\n\
         bar\r\n\
         --{0}--\r\n\
         ",
        form.boundary()
    );

    let ct = format!("multipart/form-data; boundary={}", form.boundary());

    let server = support::server::http(move |mut req| {
        let ct = ct.clone();
        let expected_body = expected_body.clone();
        async move {
            assert_eq!(req.method(), "POST");
            assert_eq!(req.headers()["content-type"], ct);
            assert_eq!(
                req.headers()["content-length"],
                expected_body.len().to_string()
            );

            let mut full: Vec<u8> = Vec::new();
            while let Some(item) = req.body_mut().frame().await {
                full.extend(&*item.unwrap().into_data().unwrap());
            }

            assert_eq!(full, expected_body.as_bytes());

            http::Response::default()
        }
    });

    let url = format!("http://{}/multipart/1", server.addr());
    let mut client = build_https_client().unwrap();
    let res = RequestBuilder::post(&url)?
        .multipart(form)?
        .send(&mut client)
        .await
        .unwrap();

    assert_eq!(res.status(), StatusCode::OK);
    Ok(())
}

#[cfg(feature = "stream")]
#[tokio::test]
async fn stream_part() -> client_util::Result<()> {
    use bytes::Bytes;
    use client_util::prelude::*;
    use futures_util::{future, stream};
    use http_body::Frame;
    let part = Part::body(boxed_stream(stream::once(future::ready(Ok::<
        _,
        client_util::error::BoxError,
    >(
        Frame::data(Bytes::from_static(b"part1 part2")),
    )))));

    let form = Form::new().text("foo", "bar").part("part_stream", part);

    let expected_body = format!(
        "\
         --{0}\r\n\
         Content-Disposition: form-data; name=\"foo\"\r\n\
         \r\n\
         bar\r\n\
         --{0}\r\n\
         Content-Disposition: form-data; name=\"part_stream\"\r\n\
         \r\n\
         part1 part2\r\n\
         --{0}--\r\n\
         ",
        form.boundary()
    );

    let ct = format!("multipart/form-data; boundary={}", form.boundary());

    let server = support::server::http(move |req| {
        let ct = ct.clone();
        let expected_body = expected_body.clone();
        async move {
            assert_eq!(req.method(), "POST");
            assert_eq!(req.headers()["content-type"], ct);
            assert_eq!(req.headers()["transfer-encoding"], "chunked");

            let full = req.collect().await.unwrap().to_bytes();

            assert_eq!(full, expected_body.as_bytes());

            http::Response::default()
        }
    });

    let url = format!("http://{}/multipart/1", server.addr());

    let res = RequestBuilder::post(&url)?
        .multipart(form)?
        .send(build_https_client().unwrap())
        .await
        .expect("Failed to post multipart");
    assert_eq!(res.status(), http::StatusCode::OK);
    Ok(())
}

#[cfg(all(feature = "stream", feature = "io-tokio"))]
#[tokio::test]
async fn read_stream_part() -> std::result::Result<(), BoxError> {
    use client_util::prelude::*;
    let file_part = Part::body(tokio_async_read(tokio::fs::File::open(file!()).await?))
        .file_name(module_path!())
        .mime_str("text/plain")?;
    let form = Form::new().part("file", file_part);
    let ct = format!("multipart/form-data; boundary={}", form.boundary());

    let server = support::server::http(move |req| {
        let ct = ct.clone();
        async move {
            assert_eq!(req.method(), "POST");
            assert_eq!(req.headers()["content-type"], ct);
            assert_eq!(req.headers()["transfer-encoding"], "chunked");

            let full = req.collect().await.unwrap().to_bytes();
            let full_str = String::from_utf8_lossy(&full);
            println!("{}", full_str);

            http::Response::default()
        }
    });

    let url = format!("http://{}/multipart/1", server.addr());

    let res = RequestBuilder::post(&url)?
        .multipart(form)?
        .send(build_https_client().unwrap())
        .await
        .expect("Failed to post multipart");
    assert_eq!(res.status(), http::StatusCode::OK);
    Ok(())
}