sark-http 0.29.2

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

use std::mem;

use common::field::{
    dynamics,
    sections::{self, CompactPlan, INVALID, PATH, PENDING, REPLACEMENT},
};
use o3::buffer::pool;
use sark_http::field::{
    Indexing, head,
    metadata::{
        block, lower,
        map::record::Span,
        section::{
            rules,
            state::{
                complete::Selection,
                decode::{Final, Indexed, Session, Start},
            },
            storage,
        },
        strings::{Cursor, Encoding},
    },
};

#[test]
fn retains_selected_range() {
    let mut scratch = storage::Scratch::default();
    let decoder = CompactPlan::try_complete(&mut scratch).expect("complete section");
    let complete = decoder.finish().expect("complete");
    assert!(matches!(&complete, Final::Request(_)));
    let (head, _, _, _) = complete.into_parts();
    let head = head.try_request().ok().expect("request");
    let (fields, pick, _) = head.into_parts();
    let range = pick.into_inner().expect("selected path");
    assert_eq!(fields.as_bytes().get(range), Some(PATH));
}

#[test]
fn duplicate_blocks_publish() {
    let mut scratch = storage::Scratch::default();
    let mut decoder = CompactPlan::try_complete(&mut scratch).expect("complete section");
    CompactPlan::try_push(&mut decoder, REPLACEMENT).expect("field");
    let complete = decoder.finish().expect("complete");
    assert!(matches!(&complete, Final::Rejected(_)));
    let (head, sealed, finished, response) = complete.into_parts();
    assert!(head.try_request().is_err());
    assert_eq!(sealed, block::Validation::Malformed);
    assert_eq!(finished, block::Validation::Malformed);
    assert_eq!(response, None);
}

#[test]
fn invalid_blocks_publish() {
    let mut scratch = storage::Scratch::default();
    let mut decoder = CompactPlan::try_complete(&mut scratch).expect("complete section");
    CompactPlan::try_push(&mut decoder, INVALID).expect("field");
    let (head, _, _, _) = decoder.finish().expect("complete").into_parts();
    assert!(head.try_request().is_err());
}

const _: () = assert!(!mem::needs_drop::<Selection<'static, 'static, CompactPlan>>());

#[test]
fn forgotten_blocks_publish() {
    for resume in [false, true] {
        let mut scratch = storage::Scratch::default();
        let mut decoder = CompactPlan::try_complete(&mut scratch).expect("complete section");
        let name = block::Named::from_span(Span::from_slice(PENDING.name));
        decoder.select(name);
        if resume {
            CompactPlan::try_push(&mut decoder, PENDING).expect("field");
        }
        let (head, _, _, _) = decoder.finish().expect("complete").into_parts();
        assert!(head.try_request().is_err());
    }
}

#[test]
fn blocks_pending_selection() {
    let lease = pool::Span::try_new(128).expect("lease");
    let mut session = Session::<rules::RetainRaw>::try_new(
        lease,
        rules::Kind::Request,
        block::Connect::Disabled,
        storage::Scratch::default(),
    )
    .map_err(|(_, error)| error)
    .expect("session");
    let name = block::Named::from_slice(dynamics::NAME);
    let start = session
        .begin(Some(name), Indexing::Allowed)
        .expect("select");
    assert!(matches!(start, Start::Buffered(())));
    let error = session
        .begin(Some(name), Indexing::Allowed)
        .err()
        .expect("pending selection");
    assert!(matches!(error, lower::Error::State(lower::State::Pending)));
    let error = session.indexed(Some(name)).err().expect("pending index");
    assert!(matches!(error, lower::Error::State(lower::State::Pending)));
    assert!(matches!(
        session.finish().1,
        Err(lower::Error::State(lower::State::Pending))
    ));
}

#[test]
fn consumes_selection_once() {
    let lease = pool::Span::try_new(128).expect("lease");
    let mut session = Session::<rules::RetainRaw>::try_new(
        lease,
        rules::Kind::Request,
        block::Connect::Disabled,
        storage::Scratch::default(),
    )
    .map_err(|(_, error)| error)
    .expect("session");
    let name = block::Named::from_slice(dynamics::NAME);
    let value = block::Valued::from_span(Span::from_slice(dynamics::VALID));
    let indexed = session.indexed(Some(name)).expect("select");
    assert!(matches!(indexed, Indexed::Buffered(())));
    let error = session
        .direct::<rules::RejectPush>()
        .expect_err("buffered selection");
    assert!(matches!(error, lower::Error::State(lower::State::Pending)));
    session
        .field::<rules::RejectPush>(name, value, Indexing::Allowed)
        .expect("selected field");
    let error = session
        .field::<rules::RejectPush>(name, value, Indexing::Allowed)
        .expect_err("consumed selection");
    assert!(matches!(error, lower::Error::State(lower::State::Ready)));
    assert!(session.finish().1.is_ok());
}

#[test]
fn mismatch_keeps_selection() {
    let lease = pool::Span::try_new(256).expect("lease");
    let mut session = Session::<rules::RetainRaw>::try_new(
        lease,
        rules::Kind::Request,
        block::Connect::Disabled,
        storage::Scratch::default(),
    )
    .map_err(|(_, error)| error)
    .expect("session");
    for field in sections::REQUEST {
        let name = block::Named::from_slice(field.name);
        let value = block::Valued::from_span(Span::from_slice(field.value));
        session.indexed(Some(name)).expect("select");
        let error = session
            .field::<rules::RejectPush>(
                block::Named::from_slice(dynamics::NAME),
                value,
                field.indexing,
            )
            .expect_err("mismatched name");
        assert!(matches!(error, lower::Error::Name { expected, actual }
            if expected == name.proof().head() && actual == head::Name::Regular));
        session
            .field::<rules::RejectPush>(name, value, field.indexing)
            .expect("original selection");
    }
    let (head, _, _, _) = session.finish().1.expect("finish").into_parts();
    let head = head.try_request().ok().expect("request");
    assert!(head.fields().iter().eq(sections::REQUEST));
}

#[test]
fn rejects_buffered_value() {
    let lease = pool::Span::try_new(256).expect("lease");
    let mut session = Session::<rules::RetainRaw>::try_new(
        lease,
        rules::Kind::Request,
        block::Connect::Disabled,
        storage::Scratch::default(),
    )
    .map_err(|(_, error)| error)
    .expect("session");
    for field in sections::REQUEST {
        let name = block::Named::from_slice(field.name);
        session.indexed(Some(name)).expect("select request field");
        session
            .field::<rules::RejectPush>(
                name,
                block::Valued::from_span(Span::from_slice(field.value)),
                field.indexing,
            )
            .expect("request field");
    }
    let name = block::Named::from_slice(dynamics::NAME);
    session.indexed(Some(name)).expect("select regular field");
    session
        .field::<rules::RejectPush>(
            name,
            block::Valued::from_span(Span::from_slice(dynamics::INVALID)),
            Indexing::Allowed,
        )
        .expect("record invalid value");
    let (head, _, _, _) = session.finish().1.expect("finish").into_parts();
    assert!(head.try_request().is_err());
}

#[test]
fn rejects_direct_value() {
    let lease = pool::Span::try_new(256).expect("lease");
    let mut session = Session::<CompactPlan>::try_new(
        lease,
        rules::Kind::Request,
        block::Connect::Disabled,
        storage::Scratch::default(),
    )
    .map_err(|(_, error)| error)
    .expect("session");
    for field in sections::REQUEST {
        let name = block::Named::from_slice(field.name);
        if name.proof().head() == head::Name::Path {
            let start = session
                .begin(Some(name), field.indexing)
                .expect("select path");
            assert!(matches!(start, Start::Direct(())));
            let invalid = dynamics::INVALID;
            let mut cursor = Cursor::new(invalid.len(), Encoding::Plain);
            for bytes in invalid.chunks(1) {
                cursor = match session.string(cursor, bytes) {
                    lower::Step::Ready { cursor } => Some(cursor),
                    _ => None,
                }
                .expect("direct string");
            }
            session
                .direct::<rules::RejectPush>()
                .expect("record direct verdict");
        } else {
            let indexed = session.indexed(Some(name)).expect("select");
            if let Indexed::Buffered(()) = indexed {
                session
                    .field::<rules::RejectPush>(
                        name,
                        block::Valued::from_span(Span::from_slice(field.value)),
                        field.indexing,
                    )
                    .expect("request field");
            }
        }
    }
    let (head, _, _, _) = session.finish().1.expect("finish").into_parts();
    assert!(head.try_request().is_err());
}