sark-http 0.29.0

HTTP wire types, semantics, codecs, and ownership invariants for Sark
use std::{convert::Infallible, ops};

use o3::buffer::pool;
use sark_http::field::{
    head,
    metadata::{
        block::{self, Validation},
        lower::{self, Lower},
        map::record::{Entry, Span},
        section,
        section::{
            rules,
            rules::{Disposition, Kind, Push, Retention},
            state::{Decoder, complete, fixed},
            storage::{Fields, Scratch, Tag},
        },
    },
};

pub const PATH: &[u8] = b"/asset";
pub const REQUEST: [Entry<'static>; 4] = [
    Entry::new(b":method", b"GET"),
    Entry::new(b":scheme", b"https"),
    Entry::new(b":path", PATH),
    Entry::new(b":authority", b"example.test"),
];
pub const HOST: Entry<'static> = Entry::new(b"host", b"example.test");
pub const REPLACEMENT: Entry<'static> = Entry::new(b":path", b"/other");
pub const PENDING: Entry<'static> = Entry::new(b"x-pending", b"");
pub const INVALID: Entry<'static> = Entry::new(b"x-invalid", b"\0");
pub const PATH_VALUES: &[(&[u8], Validation)] = &[
    (b"x", Validation::Malformed),
    (b"/a#frag", Validation::Malformed),
    (b"/%", Validation::Malformed),
    (b"/%2", Validation::Malformed),
    (b"/%xz", Validation::Malformed),
    (b"/a b", Validation::Malformed),
    (b"/a\tb", Validation::Malformed),
    (b"/", Validation::Valid),
    (b"//authority/path", Validation::Valid),
    (b"/a%23frag?x=%25&y=?", Validation::Valid),
    (b"*", Validation::Valid),
];
pub const TE_VALUES: &[(&[u8], Validation)] = &[
    (b"", Validation::Valid),
    (b",,", Validation::Valid),
    (b"trailers", Validation::Valid),
    (b"TrAiLeRs", Validation::Valid),
    (b"trailers, trailers", Validation::Valid),
    (b"trailers,\t, Trailers", Validation::Valid),
    (b",trailers,", Validation::Valid),
    (b"gzip", Validation::Malformed),
    (b"trailers;q=1", Validation::Malformed),
    (b"trailer", Validation::Malformed),
    (b"trailersx", Validation::Malformed),
    (b"trail ers", Validation::Malformed),
    (b" trailers", Validation::Malformed),
    (b"trailers\t", Validation::Malformed),
];
pub const LENGTH_VALUES: &[(&[&[u8]], Option<u64>)] = &[
    (&[], None),
    (&[b"0"], Some(0)),
    (&[b"18446744073709551615"], Some(u64::MAX)),
    (&[b"5", b"005", b"5 \t,\t005"], Some(5)),
];
pub const LENGTH_INVALID: &[&[&[u8]]] = &[
    &[b""],
    &[b"+5"],
    &[b"5x"],
    &[b"5 5"],
    &[b"5\t5"],
    &[b",5"],
    &[b"5,"],
    &[b"5,,5"],
    &[b"18446744073709551616"],
    &[b"5, 9"],
    &[b"5", b"9"],
];
pub const LENGTH_CONFLICT: [Entry<'static>; 2] = [
    Entry::new(b"content-length", b"5"),
    Entry::new(b"content-length", b"9"),
];
pub const LENGTH_ZERO: Entry<'static> = Entry::new(b"content-length", b"0");
pub const LENGTH_RESPONSES: &[(&[u8], u16, Entry<'static>, Validation)] = &[
    (b"103", 103, LENGTH_ZERO, Validation::Malformed),
    (b"204", 204, LENGTH_CONFLICT[0], Validation::Valid),
    (b"205", 205, LENGTH_CONFLICT[0], Validation::Valid),
    (b"304", 304, LENGTH_CONFLICT[0], Validation::Valid),
];

#[derive(Debug)]
pub enum Error {
    Storage(pool::CreateError),
    Lower(lower::Error),
}

pub struct Authority;

impl Push for Authority {
    const SCHEMES: Option<&[&[u8]]> = Some(&[b"https"]);
    const AUTHORITIES: Option<&[&[u8]]> = Some(&[b"example.test"]);
}

#[derive(Default)]
pub struct CompactPlan {
    path: Option<ops::Range<usize>>,
}

impl section::Policy for CompactPlan {
    type Selection = Option<ops::Range<usize>>;
    type Block = Fields;

    fn finish(self) -> Self::Selection {
        self.path
    }
}

impl rules::Plan for CompactPlan {
    type Inspect = Infallible;
    const SKIP_DISCARDED: bool = false;

    fn disposition(input: rules::Candidate<'_, Self>) -> Disposition<Self::Inspect> {
        let (_, known) = input.identify();
        if known == head::Name::Path {
            Disposition::Tagged(Tag::PATH)
        } else {
            Disposition::Discard
        }
    }

    fn inspect(token: Self::Inspect, _: rules::Observation<'_, Self>) -> Validation {
        match token {}
    }

    fn commit(input: rules::Commit<'_, Self>) -> Validation {
        let (this, tag, range) = input.store();
        if tag == Tag::PATH {
            this.path = Some(range);
        }
        Validation::Valid
    }
}

impl CompactPlan {
    pub fn try_complete(
        scratch: &mut Scratch,
    ) -> Result<complete::Decoder<'_, CompactPlan>, Error> {
        let lease = pool::Span::try_new(128).map_err(Error::Storage)?;
        let mut decoder = complete::Decoder::try_new(
            lease,
            scratch,
            rules::Kind::Request,
            block::Connect::Disabled,
        )
        .map_err(Error::Lower)?;
        for field in REQUEST {
            Self::try_push(&mut decoder, field).map_err(Error::Lower)?;
        }
        Ok(decoder)
    }

    pub fn try_push(
        decoder: &mut complete::Decoder<'_, CompactPlan>,
        field: Entry<'_>,
    ) -> Result<(), lower::Error> {
        use complete::Selection;
        use sark_http::field::metadata::map::record::Span;

        let name = block::Named::from_span(Span::from_slice(field.name));
        let value = block::Valued::from_span(Span::from_slice(field.value));
        match decoder.select(name) {
            Selection::Skipped(field) => field.validate(value.validation()),
            Selection::Buffered(selected) => selected.push::<Authority>(value, field.indexing)?,
        }
        Ok(())
    }
}

impl Authority {
    pub fn validation(kind: Kind, fields: &[Entry<'_>]) -> Validation {
        let mut section = fixed::Raw::new(kind);
        for field in fields {
            let value = block::Valued::from_span(Span::from_slice(field.value));
            rules::Validate::value(&mut section, value.validation());
            let proof = block::Name::classify(field.name);
            if section.accept(proof) == block::Validation::Valid {
                match section.retain::<Authority>(proof.head(), (*field).into(), &[], &[]) {
                    Retention::Retain | Retention::Discard => {}
                }
            }
        }
        section.finish().1
    }

    pub fn is_valid(kind: Kind, fields: &[Entry<'_>]) -> bool {
        Self::validation(kind, fields) == Validation::Valid
    }

    pub fn request<'a>(method: &'a [u8], extra: Option<Entry<'a>>) -> Vec<Entry<'a>> {
        let mut fields = vec![
            Entry::new(b":method", method),
            Entry::new(b":scheme", b"https"),
            Entry::new(b":path", b"/asset"),
            Entry::new(b":authority", b"example.test"),
        ];
        fields.extend(extra);
        fields
    }
}

pub trait Apply {
    fn try_lower<A: Push>(
        &mut self,
        lease: &mut pool::Span,
        scratch: &mut Scratch,
        field: Entry<'_>,
    ) -> Result<(), lower::Error>;
}

impl<P: rules::Plan> Apply for Decoder<P> {
    fn try_lower<A: Push>(
        &mut self,
        lease: &mut pool::Span,
        scratch: &mut Scratch,
        field: Entry<'_>,
    ) -> Result<(), lower::Error> {
        let name = Span::from_slice(field.name);
        let value = Span::from_slice(field.value);
        let mut lower = Lower::new(lease, scratch, self);
        let decision = lower.disposition(name, block::Name::classify(field.name));
        let push = lower.try_push_as(decision, name, value, field.indexing)?;
        push.commit::<A>();
        Ok(())
    }
}