ohkami 0.24.9

A performant, declarative, and runtime-flexible web framework for Rust
Documentation
#![cfg(all(test, feature = "__rt_native__", feature = "DEBUG"))]

use crate::Response;

macro_rules! assert_response_bytes_eq {
    ($res:expr, $expected:literal) => {
        {
            $res.complete();

            /* avoid flakiness of 1 sec difference */
            let now = $res.headers.date()
                .expect("No `Date` header in res")
                .to_string(/* `$res` moves by `.send` */);

            let mut res_bytes = Vec::new();
            crate::__rt__::testing::block_on(
                $res.send(&mut res_bytes)
            ).expect("`Response::send` failed");

            if res_bytes != format!($expected, NOW = now).into_bytes() {
                panic!("\n\
                    [got]\n\
                    {}\n\
                    [expected]\n\
                    {}\n",
                    (res_bytes).escape_ascii(),
                    $expected,
                )
            }
        }
    };
}

#[test]
fn test_response_into_bytes() {
    let mut res = Response::NoContent();
    assert_response_bytes_eq!(
        res,
        "\
        HTTP/1.1 204 No Content\r\n\
        Date: {NOW}\r\n\
        \r\n\
    "
    );

    let mut res = Response::NoContent();
    res.headers.set().server("ohkami");
    assert_response_bytes_eq!(
        res,
        "\
        HTTP/1.1 204 No Content\r\n\
        Date: {NOW}\r\n\
        Server: ohkami\r\n\
        \r\n\
    "
    );

    let mut res = Response::NotFound();
    assert_response_bytes_eq!(
        res,
        "\
        HTTP/1.1 404 Not Found\r\n\
        Date: {NOW}\r\n\
        Content-Length: 0\r\n\
        \r\n\
    "
    );

    let mut res = Response::NotFound();
    res.headers
        .set()
        .server("ohkami")
        .x("Hoge-Header", "Something-Custom");
    assert_response_bytes_eq!(
        res,
        "\
        HTTP/1.1 404 Not Found\r\n\
        Date: {NOW}\r\n\
        Content-Length: 0\r\n\
        Server: ohkami\r\n\
        Hoge-Header: Something-Custom\r\n\
        \r\n\
    "
    );

    let mut res = Response::NotFound();
    res.headers
        .set()
        .server("ohkami")
        .x("Hoge-Header", "Something-Custom")
        .set_cookie("id", "42", |d| d.path("/").same_site_lax())
        .set_cookie("name", "John", |d| d.path("/where").same_site_strict());
    assert_response_bytes_eq!(
        res,
        "\
        HTTP/1.1 404 Not Found\r\n\
        Date: {NOW}\r\n\
        Content-Length: 0\r\n\
        Server: ohkami\r\n\
        Hoge-Header: Something-Custom\r\n\
        Set-Cookie: id=42; Path=/; SameSite=Lax\r\n\
        Set-Cookie: name=John; Path=/where; SameSite=Strict\r\n\
        \r\n\
    "
    );

    let mut res = Response::NotFound().with_text("sample text");
    res.headers
        .set()
        .server("ohkami")
        .x("Hoge-Header", "Something-Custom")
        .set_cookie("id", "42", |d| d.path("/").same_site_lax())
        .set_cookie("name", "John", |d| d.path("/where").same_site_strict());
    assert_response_bytes_eq!(
        res,
        "\
        HTTP/1.1 404 Not Found\r\n\
        Date: {NOW}\r\n\
        Content-Length: 11\r\n\
        Content-Type: text/plain; charset=UTF-8\r\n\
        Server: ohkami\r\n\
        Hoge-Header: Something-Custom\r\n\
        Set-Cookie: id=42; Path=/; SameSite=Lax\r\n\
        Set-Cookie: name=John; Path=/where; SameSite=Strict\r\n\
        \r\n\
        sample text\
    "
    );
}

#[cfg(feature = "sse")]
#[test]
fn test_stream_response() {
    let __now__ = ::ohkami_lib::imf_fixdate(crate::util::unix_timestamp());

    fn repeat_by<T, F: Fn(usize) -> T + Unpin>(
        n: usize,
        f: F,
    ) -> impl ohkami_lib::Stream<Item = T> {
        struct Repeat<F> {
            f: F,
            n: usize,
            count: usize,
        }
        const _: () = {
            impl<T, F: Fn(usize) -> T + Unpin> ohkami_lib::Stream for Repeat<F> {
                type Item = T;
                fn poll_next(
                    mut self: std::pin::Pin<&mut Self>,
                    _cx: &mut std::task::Context<'_>,
                ) -> std::task::Poll<Option<Self::Item>> {
                    if self.count < self.n {
                        let item = std::task::Poll::Ready(Some((self.f)(self.count)));
                        self.as_mut().count += 1;
                        item
                    } else {
                        std::task::Poll::Ready(None)
                    }
                }
            }
        };

        Repeat { f, n, count: 0 }
    }

    let mut res = Response::OK()
        .with_stream(repeat_by(3, |i| format!("This is message#{i} !")))
        .with_headers(|h| {
            h.server("ohkami")
                .x("is-stream", "true")
                .set_cookie("name", "John", |d| d.path("/where").same_site_strict())
        });
    assert_response_bytes_eq!(
        res,
        "\
        HTTP/1.1 200 OK\r\n\
        Date: {NOW}\r\n\
        Content-Type: text/event-stream\r\n\
        Cache-Control: no-cache, must-revalidate\r\n\
        Transfer-Encoding: chunked\r\n\
        Server: ohkami\r\n\
        is-stream: true\r\n\
        Set-Cookie: name=John; Path=/where; SameSite=Strict\r\n\
        \r\n\
        1b\r\n\
        data: This is message#0 !\n\
        \n\
        \r\n\
        1b\r\n\
        data: This is message#1 !\n\
        \n\
        \r\n\
        1b\r\n\
        data: This is message#2 !\n\
        \n\
        \r\n\
        0\r\n\
        \r\n\
    "
    );

    let mut res = Response::OK()
        .with_stream(repeat_by(3, |i| format!("This is message#{i}\nです")))
        .with_headers(|h| {
            h.server("ohkami")
                .set_cookie("name", "John", |d| d.path("/where").same_site_strict())
                .x("is-stream", "true")
        });
    assert_response_bytes_eq!(
        res,
        "\
        HTTP/1.1 200 OK\r\n\
        Date: {NOW}\r\n\
        Content-Type: text/event-stream\r\n\
        Cache-Control: no-cache, must-revalidate\r\n\
        Transfer-Encoding: chunked\r\n\
        Server: ohkami\r\n\
        is-stream: true\r\n\
        Set-Cookie: name=John; Path=/where; SameSite=Strict\r\n\
        \r\n\
        26\r\n\
        data: This is message#0\n\
        data: です\n\
        \n\
        \r\n\
        26\r\n\
        data: This is message#1\n\
        data: です\n\
        \n\
        \r\n\
        26\r\n\
        data: This is message#2\n\
        data: です\n\
        \n\
        \r\n\
        0\r\n\
        \r\n\
    "
    );
}