#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Part<'a> {
Method(&'a [u8]),
Path(&'a [u8]),
Header(&'a [u8], &'a [u8]),
Chunk(&'a [u8]),
End,
}
pub trait PartSource {
fn next(&mut self) -> Option<Part<'_>>;
}
pub trait PartSink {
type Err;
fn push(&mut self, part: Part<'_>) -> Result<(), Self::Err>;
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::{Part, PartSink, PartSource};
struct SliceSource<'a> {
parts: &'a [Part<'a>],
cursor: usize,
}
impl PartSource for SliceSource<'_> {
fn next(&mut self) -> Option<Part<'_>> {
let part = *self.parts.get(self.cursor)?;
self.cursor += 1;
Some(part)
}
}
#[derive(Default)]
struct CountingSink {
headers_seen: usize,
ended: bool,
}
impl PartSink for CountingSink {
type Err = &'static str;
fn push(&mut self, part: Part<'_>) -> Result<(), Self::Err> {
match part {
Part::Header(_, _) => {
self.headers_seen += 1;
Ok(())
}
Part::End => {
self.ended = true;
Ok(())
}
Part::Method(_) | Part::Path(_) | Part::Chunk(_) => Ok(()),
}
}
}
#[test]
fn part_source_steps_one_borrowed_part_at_a_time() {
let parts = [
Part::Method(b"GET"),
Part::Path(b"/health"),
Part::Header(b"accept", b"*/*"),
Part::End,
];
let mut source = SliceSource {
parts: &parts,
cursor: 0,
};
assert_eq!(source.next(), Some(Part::Method(b"GET")));
assert_eq!(source.next(), Some(Part::Path(b"/health")));
assert_eq!(source.next(), Some(Part::Header(b"accept", b"*/*")));
assert_eq!(source.next(), Some(Part::End));
assert_eq!(source.next(), None);
}
#[test]
fn part_sink_absorbs_parts_and_reports_counts() {
let parts = [
Part::Method(b"POST"),
Part::Header(b"content-type", b"application/json"),
Part::Header(b"x-request-id", b"abc123"),
Part::End,
];
let mut source = SliceSource {
parts: &parts,
cursor: 0,
};
let mut sink = CountingSink::default();
while let Some(part) = source.next() {
sink.push(part).expect("counting sink never rejects");
}
assert_eq!(sink.headers_seen, 2);
assert!(sink.ended);
}
#[test]
fn part_sink_can_reject() {
struct FullSink;
impl PartSink for FullSink {
type Err = &'static str;
fn push(&mut self, _part: Part<'_>) -> Result<(), Self::Err> {
Err("capacity exceeded")
}
}
let mut sink = FullSink;
let error = sink
.push(Part::Header(b"a", b"b"))
.expect_err("full sink rejects");
assert_eq!(error, "capacity exceeded");
}
}