sark 0.12.0

Simple Asynchronous Rust webKit - Server
Documentation
#![allow(clippy::too_many_arguments)]

mod support;

use std::io::{Read, Write};
use std::net::TcpStream;
use std::time::Duration;

use dope_test::Harness;
use http::StatusCode;
use o3::buffer::{Bytes, Retained};
use sark::{Executor, Throughput, driver};

#[sark_gen::request]
struct MinimalReq {}

#[sark_gen::request(full)]
struct FullReq {}

#[sark_gen::request]
struct RoutedReq {
    #[path("id", default = "")]
    id: Bytes<Retained>,
    #[query("tag", default = "")]
    tag: Bytes<Retained>,
}

#[sark_gen::response(raw)]
struct Reply {
    status: StatusCode,
    body: &'static [u8],
}

#[sark_gen::handler]
fn minimal(_req: MinimalReq, _state: &()) -> Reply {
    Reply {
        status: StatusCode::OK,
        body: b"minimal",
    }
}

#[sark_gen::handler]
fn full(_req: FullReq, _state: &()) -> Reply {
    Reply {
        status: StatusCode::OK,
        body: b"full",
    }
}

#[sark_gen::handler]
fn routed(req: RoutedReq, _state: &()) -> Reply {
    Reply {
        status: if req.id.as_slice() == b"42" && req.tag.as_slice() == b"rust" {
            StatusCode::IM_A_TEAPOT
        } else {
            StatusCode::OK
        },
        body: b"routed",
    }
}

sark_gen::define_route! {
    SelectiveDispatch: () => {
        GET "/minimal" => minimal,
        GET "/full" => full,
        GET "/items/:id" => routed,
    }
}

fn exchange(addr: std::net::SocketAddr, target: &[u8]) -> Vec<u8> {
    let mut stream = TcpStream::connect(addr).expect("connect");
    stream
        .set_read_timeout(Some(Duration::from_secs(2)))
        .expect("set timeout");
    let mut request = b"GET ".to_vec();
    request.extend_from_slice(target);
    request.extend_from_slice(b" HTTP/1.1\r\nHost: example\r\nConnection: close\r\n\r\n");
    stream.write_all(&request).expect("send request");
    let mut response = Vec::new();
    stream.read_to_end(&mut response).expect("read response");
    response
}

fn status(response: &[u8]) -> &[u8] {
    response.get(9..12).expect("HTTP status")
}

#[test]
fn generated_routes_parse_only_declared_capabilities() {
    let bind: std::net::SocketAddr = "127.0.0.1:38767".parse().unwrap();
    let server = support::http_server(bind, Duration::from_secs(10));

    Harness::new(bind)
        .run_with_trigger(
            |_ctx, trigger| {
                let config =
                    driver::Config::for_tcp_profile::<Throughput>(support::MAX_CONNECTIONS);
                let executor = Executor::new(config)?
                    .with_storage(dope_net::link::egress::storage::Storage::default());
                executor.enter(|mut session| {
                    let timer =
                        sark::Timer::with_capacity(support::MAX_CONNECTIONS.saturating_mul(2));
                    server.clone().serve(
                        &mut session,
                        SelectiveDispatch::new(
                            &(),
                            &timer,
                            sark::app::Config {
                                timer_capacity: support::MAX_CONNECTIONS.saturating_mul(2),
                                task_capacity: support::MAX_CONNECTIONS,
                            },
                        ),
                        Some(trigger),
                    )
                })
            },
            |addr| {
                assert_eq!(status(&exchange(addr, b"/minimal?ignored=yes")), b"200");
                assert_eq!(status(&exchange(addr, b"/items/42?tag=rust")), b"418");

                let invalid_unused_query = b"/minimal?ignored=\x01";
                assert_eq!(status(&exchange(addr, invalid_unused_query)), b"200");

                let invalid_full_query = b"/full?ignored=\x01";
                assert_eq!(status(&exchange(addr, invalid_full_query)), b"400");
            },
        )
        .expect("harness");
}