Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for http-body v1 #161

Merged
merged 1 commit into from
Jan 14, 2024
Merged

Add support for http-body v1 #161

merged 1 commit into from
Jan 14, 2024

Conversation

nwtgck
Copy link
Contributor

@nwtgck nwtgck commented Jan 13, 2024

closes #160

http_body::Body trait

Example 1

use auto_enums::auto_enum;
use http_body_util::BodyExt as _;
use std::convert::Infallible;
use std::net::SocketAddr;

#[auto_enum(http_body1::Body)]
fn create_body(
    req: hyper::Request<hyper::body::Incoming>,
) -> impl hyper::body::Body<Data = hyper::body::Bytes, Error = hyper::Error> {
    match req.method() {
        // "hello world"
        &hyper::Method::GET => {
            let body: http_body_util::combinators::BoxBody<hyper::body::Bytes, hyper::Error> =
                http_body_util::Full::new(hyper::body::Bytes::from("Hello, World!\n"))
                    .map_err(|_: Infallible| unreachable!())
                    .boxed();
            body
        }
        // Response body is request body (echo-like)
        _ => {
            let body: hyper::body::Incoming = req.into_body();
            body
        }
    }
}
async fn handle(
    req: hyper::Request<hyper::body::Incoming>,
) -> Result<
    hyper::Response<impl hyper::body::Body<Data = hyper::body::Bytes, Error = hyper::Error>>,
    Infallible,
> {
    Ok(hyper::Response::new(create_body(req)))
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    let tcp_listener = tokio::net::TcpListener::bind(addr).await?;
    loop {
        let (stream, _) = tcp_listener.accept().await?;
        tokio::task::spawn(async move {
            if let Err(err) = hyper::server::conn::http1::Builder::new()
                .serve_connection(
                    hyper_util::rt::TokioIo::new(stream),
                    hyper::service::service_fn(handle),
                )
                .await
            {
                println!("Error serving connection: {:?}", err);
            }
        });
    }
}
$ curl localhost:3000
Hello, World!

$ curl --data "this message should be echoed" localhost:3000
this message should be echoed

Limitation?

I tried to use hyper::Response<impl Body<...>> as return type but a compile error ocurred.

// compile error code

#[auto_enum(http_body1::Body)]
async fn handle(
    req: hyper::Request<hyper::body::Incoming>,
) -> hyper::Response<impl hyper::body::Body<Data = hyper::body::Bytes, Error = hyper::Error>> {
    match req.method() {
        // "hello world"
        &Method::GET => {
            let body: http_body_util::combinators::BoxBody<hyper::body::Bytes, hyper::Error> =
                http_body_util::Full::new(hyper::body::Bytes::from("Hello, World!\n"))
                    .map_err(|_: Infallible| unreachable!())
                    .boxed();
            hyper::Response::new(body)
        }
        // Response body is request body (echo-like)
        // Body type is hyper::body::Incoming
        _ => hyper::Response::new(req.into_body()),
    }
}
28 | #[auto_enum(http_body1::Body)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Response<_>`, found `__Enum6727346999787098788<Response<...>, ...>`
   |
   = note: expected struct `Response<_>`
                found enum `__Enum6727346999787098788<Response<BoxBody<hyper::body::Bytes, hyper::Error>>, _>`
   = note: this error originates in the attribute macro `auto_enum` (in Nightly builds, run with -Z macro-backtrace for more info)

Return type of hyper::Response<impl Body<...>> (and Result<hyper::Response<impl Body<...>>, ...>) should be a very common ussage in hyper.

workaround using enum_derive
// works

async fn handle_with_enum_drive(
    req: hyper::Request<hyper::body::Incoming>,
) -> Result<
    hyper::Response<impl hyper::body::Body<Data = hyper::body::Bytes, Error = hyper::Error>>,
    Infallible,
> {
    #[auto_enums::enum_derive(http_body1::Body)]
    enum BodyEnum<B1, B2> {
        Body1(B1),
        Body2(B2),
    }

    match req.method() {
        // "hello world"
        &hyper::Method::GET => {
            let body: http_body_util::combinators::BoxBody<hyper::body::Bytes, hyper::Error> =
                http_body_util::Full::new(hyper::body::Bytes::from("Hello, World!\n"))
                    .map_err(|_: Infallible| unreachable!())
                    .boxed();
            Ok(hyper::Response::new(BodyEnum::Body1(body)))
        }
        // Response body is request body (echo-like)
        // Body type is hyper::body::Incoming
        _ => Ok(hyper::Response::new(BodyEnum::Body2(req.into_body()))),
    }
}

Limitation? (simple)

// compile error code

#[auto_enum(Iterator)]
fn foo(x: i32) -> Option<impl Iterator<Item = i32>> {
    match x {
        0 => Some(1..10),
        1 => None,
        _ => Some(vec![5, 10].into_iter()),
    }
}
62 | #[auto_enum(Iterator)]
   | ^^^^^^^^^^^^^^^^^^^^^^ expected `Option<_>`, found `__Enum375715025964148285<Option<...>, ..., ...>`
63 | fn foo(x: i32) -> Option<impl Iterator<Item = i32>> {
   |                   --------------------------------- expected `Option<_>` because of return type

edit: I understand using "?" solves the issue:

#[auto_enum]
fn return3(x: i32) -> Option<impl Iterator<Item = i32>> {
if x < 0 {
return None;
}
#[auto_enum(Iterator)]
let iter = match x {
0 => 2..8,
1 => None?,
2 => std::option::Option::None?,
3 => core::option::Option::None?,
4 => None::<_>?,
_ => 2..=10,
};
Some(iter)
}
assert_eq!(return3(10).unwrap().sum::<i32>(), 54);

Some marker needed like the following #[auto_enum] 1..10? I'm not sure that macro implementation can know 1..10 and vec[...].into_iter() implement Iterator<Item = i32> and generate code wrapped with __Enum.

// imaginary code

#[auto_enum(Iterator)]
fn foo(x: i32) -> Option<impl Iterator<Item = i32>> {
    match x {
        0 => Some(#[auto_enum] 1..10),
        1 => None,
        _ => Some(#[auto_enum] vec![5, 10].into_iter()),
    }
}

@nwtgck nwtgck force-pushed the http-body-1.0 branch 4 times, most recently from 7870375 to b4dc4f1 Compare January 13, 2024 15:27
@nwtgck nwtgck marked this pull request as ready for review January 13, 2024 15:30
@taiki-e
Copy link
Owner

taiki-e commented Jan 14, 2024

Thanks! This looks good to me.

Limitation? (simple)

This is a known limitation. See #126 and #106.

It is possible to implement this for Option, Result, tuple, etc., but it would be more difficult to handle impl traits wrapped in external crate types. We cannot write such a process without understanding what an impl trait that appears in parameters means and which methods they are associated with.

Some marker needed like the following #[auto_enum] 1..10? I'm not sure that macro implementation can know 1..10 and vec[...].into_iter() implement Iterator<Item = i32> and generate code wrapped with __Enum.

The marker for this already exists. See https://docs.rs/auto_enums/latest/auto_enums/#expression-level-marker-marker-macro

@taiki-e taiki-e merged commit 3d2a23c into taiki-e:main Jan 14, 2024
10 checks passed
@taiki-e
Copy link
Owner

taiki-e commented Jan 14, 2024

Published in v0.8.4.

@nwtgck nwtgck deleted the http-body-1.0 branch January 14, 2024 09:16
@nwtgck
Copy link
Contributor Author

nwtgck commented Jan 14, 2024

Thanks for the review and the quick release!

I expected the marker must be explicitly specified.
The following codes are my imaginary implementations. They are redundant but I assume they would be handled without type and trait information.

// imaginary code

#[auto_enum_explicit(Iterator)] // requires explicit marker
fn foo(x: i32) -> impl Iterator<Item = i32> {
    match x {
        0 => 1..10,                   // compile ERROR because of no marker
        _ => vec![5, 10].into_iter(), // compile ERROR because of no marker
    }
}
// imaginary code

#[auto_enum_explicit(Iterator)] // requires explicit markers
fn foo(x: i32) -> impl Iterator<Item = i32> {
    match x {
        0 => enum!(1..10,)                   // compile passed
        _ => enum!(vec![5, 10].into_iter()), // compile passed
    }
}
// imaginary code

fn return4(x: i32) -> Result<impl Iterator<Item = i32>, ()> {
        if x < 0 {
            return Err(());
        }

        #[auto_enum_explicit(Iterator)] // requires explicit markers
        let iter = match x {
            0 => enum!(2..8),                          // compile passed
            1 => Err(())?,
            2 => std::result::Result::Err(())?,
            3 => core::result::Result::Err(())?,
            4 => Err::<_, ()>(())?,
            _ => enum!(2..=10),                        // compile passed
        };

        Ok(iter)
    }
// imaginary code

#[auto_enum_explicit(Iterator)] // requres explicit markers
fn foo(x: i32) -> impl Iterator<Item = i32> {
    match x {
        0 => enum!(1..10),
        // The #[nested] attribute might not needed with explicit markers because each branch (if, match) containing enum! markers has one `enum __Enum` definition.
        // #[nested]
        _ => match x {
            1 => enum!(vec![5, 10].into_iter()), // compile passed
            _ => enum!(0..=x),                   // compile passed
        },
    }
}

Sorry for off topic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

hyper 1.x hyper::body::Body
2 participants