sark-http 0.29.5

HTTP wire types, semantics, codecs, and ownership invariants for Sark
pub mod common;

use std::{num::NonZero, ops::ControlFlow};

use common::head::{self, budget};
use dope_test::alloc::Tracker;
use o3::buffer::{
    bytes::window::Snapshot,
    storage::AllocError,
    write::{Sink as _, consume},
};
use sark_http::{
    h1::{
        field::scan::{Progress, State},
        head::{
            Boundary, Limit, ParseError,
            line::{self, Request},
            request::scan,
        },
    },
    method::Known,
};

#[global_allocator]
static ALLOCATOR: Tracker = Tracker::new();

#[test]
fn retains_line_and_fields() {
    let input = b"GET /items?q=1 HTTP/1.1\r\nHost: example.test\r\n";
    let request = Request::parse(input).expect("line").expect("complete");
    let (source, allocs) = Tracker::count(|| request.try_snapshot::<256>(NonZero::<usize>::MIN));
    let source = source.expect("owner");
    assert_eq!(allocs, 1);
    let state = State::try_new(source, request.end(), 0, 2, 64)
        .ok()
        .expect("fields");
    let mut fields = 0;
    let progress = state
        .advance(|_| {
            fields += 1;
            Ok::<_, ()>(())
        })
        .ok()
        .expect("prefix");
    let Progress::More(mut state) = progress else {
        assert!(matches!(progress, Progress::More(_)));
        return;
    };
    state.try_extend(b"\r\nBODY").expect("append");
    let progress = state
        .advance(|_| {
            fields += 1;
            Ok::<_, ()>(())
        })
        .ok()
        .expect("suffix");
    let Progress::Complete(complete) = progress else {
        assert!(matches!(progress, Progress::Complete(_)));
        return;
    };
    let (source, _, end) = complete.into_parts();
    source.with_shared(|view, retained| {
        assert_eq!(retained.method(), request.method());
        assert_eq!(retained.target(), request.target());
        assert_eq!(retained.path(), Some(b"/items".as_slice()));
        assert_eq!(retained.query(), Some(b"q=1".as_slice()));
        assert_eq!(&view.as_slice()[end.get() as usize..], b"BODY");
    });
    assert_eq!(fields, 1);
}

#[test]
fn continues_borrowed_line() {
    let input = budget::REQUEST;
    let line = Request::parse_with(input, Known::Post.bit())
        .expect("line")
        .expect("complete");
    let end = line.end();
    let error = scan::State::try_request(line, Limit::DEFAULT.with_bytes(end - 1))
        .err()
        .expect("line limit");
    assert_eq!(
        error,
        ParseError::Boundary(Boundary::TooLarge {
            actual: end,
            limit: end - 1,
        }),
    );
    let state = scan::State::try_request(line, Limit::DEFAULT).expect("fields");
    let mut fields = 0;
    let progress = state
        .advance(|_| {
            fields += 1;
            Ok::<_, ()>(())
        })
        .expect("head");
    let scan::Progress::Complete(head) = progress else {
        assert!(matches!(progress, scan::Progress::Complete(_)));
        return;
    };
    assert_eq!(head.as_ref().bytes().as_ptr(), input.as_ptr());
    assert_eq!(fields, 2);
}

#[test]
fn retains_scanned_fields() {
    let input = &budget::REQUEST[..budget::REQUEST.len() - 2];
    let request = Request::parse(input).expect("line").expect("complete");
    let state = scan::State::try_request(request, Limit::DEFAULT).expect("fields");
    let mut fields = 0;
    let progress = state
        .advance(|_| {
            fields += 1;
            Ok::<_, ()>(())
        })
        .expect("prefix");
    let state = match progress {
        scan::Progress::More(state) => Some(state),
        scan::Progress::Complete(_) => None,
    }
    .expect("partial");
    let (snapshot, allocs) = Tracker::count(|| state.try_snapshot::<256>(NonZero::<usize>::MIN));
    let mut snapshot = snapshot.expect("owner");
    assert_eq!(allocs, 1);
    snapshot.try_extend(b"\r\n").expect("suffix");
    let progress = snapshot
        .advance(|_| {
            fields += 1;
            Ok::<_, ()>(())
        })
        .ok()
        .expect("head");
    let head = match progress {
        scan::Progress::Complete(head) => Some(head),
        scan::Progress::More(_) => None,
    }
    .expect("complete");
    assert_eq!(head.as_ref().bytes(), budget::REQUEST);
    assert_eq!(fields, 2);
}

#[test]
fn snapshot_rejects_before_allocation() {
    let request = Request::parse(budget::REQUEST)
        .expect("line")
        .expect("complete");
    let state = scan::State::try_request(request, Limit::DEFAULT).expect("fields");
    let (errors, allocs) = Tracker::count(|| {
        [
            request.try_snapshot::<8>(NonZero::<usize>::MIN).err(),
            state.try_snapshot::<8>(NonZero::<usize>::MIN).err(),
        ]
    });
    assert_eq!(allocs, 0);
    for error in errors {
        assert_eq!(
            error,
            Some(AllocError::Capacity(consume::Error::new(
                NonZero::new(budget::REQUEST.len()).expect("request"),
                8,
            ))),
        );
    }
}

#[test]
fn continues_owned_line() {
    let input = budget::REQUEST;
    for (methods, matched) in [
        (Known::Get.bit(), Some(Known::Get)),
        (Known::Post.bit(), None),
    ] {
        let mut source = Snapshot::<256>::new();
        source.write_slices([input, input]).expect("input");
        let pointer = source.as_ref().as_ptr();
        let source = match line::Owned::try_parse(source, methods).ok().expect("line") {
            ControlFlow::Continue(source) => Some(source),
            ControlFlow::Break(_) => None,
        }
        .expect("complete");
        assert!(source.request().matched() == matched);
        assert_eq!(source.request().method().as_ptr(), pointer);
        let end = source.request().end();
        let rejected = scan::State::try_line(source, Limit::DEFAULT.with_bytes(end - 1))
            .err()
            .expect("line limit");
        let (source, error) = rejected.into_parts();
        assert_eq!(
            error,
            ParseError::Boundary(Boundary::TooLarge {
                actual: end,
                limit: end - 1,
            }),
        );
        let state = scan::State::try_line(source, Limit::DEFAULT.with_bytes(input.len()))
            .ok()
            .expect("fields");
        let mut fields = 0;
        let progress = state
            .advance(|_| {
                fields += 1;
                Ok::<_, ()>(())
            })
            .ok()
            .expect("head");
        let scan::Progress::Complete(head) = progress else {
            assert!(matches!(progress, scan::Progress::Complete(_)));
            return;
        };
        let route = head.into_route();
        assert_eq!(route.as_ref().bytes().as_ptr(), pointer);
        assert_eq!(route.as_ref().body(), input);
        assert_eq!(fields, 2);
    }
}

#[test]
fn continues_owned_fields() {
    let input = budget::REQUEST;
    let mut source =
        Snapshot::<256>::try_with(NonZero::new(128).expect("capacity")).expect("owner");
    source
        .write_slice(&input[..input.len() - 2])
        .expect("prefix");
    let pointer = source.as_ref().as_ptr();
    let source = match line::Owned::try_parse(source, Known::ALL)
        .ok()
        .expect("line")
    {
        ControlFlow::Continue(source) => Some(source),
        ControlFlow::Break(_) => None,
    }
    .expect("complete");
    let state = scan::State::try_line(source, Limit::DEFAULT)
        .ok()
        .expect("fields");
    let progress = state.advance(|_| Ok::<_, ()>(())).ok().expect("prefix");
    let scan::Progress::More(mut state) = progress else {
        assert!(matches!(progress, scan::Progress::More(_)));
        return;
    };
    state.try_extend(&input[input.len() - 2..]).expect("suffix");
    let progress = state.advance(|_| Ok::<_, ()>(())).ok().expect("head");
    let scan::Progress::Complete(head) = progress else {
        assert!(matches!(progress, scan::Progress::Complete(_)));
        return;
    };
    assert_eq!(head.as_ref().bytes(), input);
    assert_eq!(head.as_ref().bytes().as_ptr(), pointer);
}

#[test]
fn returns_unparsed_owner() {
    let input = head::ESCAPED_REQUEST_LINE;
    let mut source = Snapshot::<256>::new();
    source
        .write_slices([&input[..input.len() - 4], &input[input.len() - 2..]])
        .expect("input");
    let pointer = source.as_ref().as_ptr();
    assert!(matches!(Request::parse(source.as_ref()), Ok(None)));
    let source = match line::Owned::try_parse(source, Known::ALL)
        .ok()
        .expect("partial")
    {
        ControlFlow::Break(source) => Some(source),
        ControlFlow::Continue(_) => None,
    }
    .expect("partial");
    assert_eq!(source.as_ref().as_ptr(), pointer);

    let mut source = Snapshot::<256>::new();
    source.write_slice(head::CONNECT_ORIGIN).expect("input");
    let pointer = source.as_ref().as_ptr();
    let expected = Request::parse(source.as_ref()).err().expect("target");
    let rejected = line::Owned::try_parse(source, Known::ALL)
        .err()
        .expect("target");
    let (source, error) = rejected.into_parts();
    assert_eq!(source.as_ref().as_ptr(), pointer);
    assert_eq!(source.as_ref(), head::CONNECT_ORIGIN);
    assert_eq!(error, expected);
}