use std::io::{Read, Write};
pub trait CodecWriter<I: ?Sized> {
type Error: std::error::Error + Send + Sync + 'static;
fn write(&mut self, item: &I) -> Result<(), Self::Error>;
fn finish(self) -> Result<(), Self::Error>;
}
pub trait CodecCursor<T> {
type Error: std::error::Error + Send + Sync + 'static;
type Current<'a>
where
Self: 'a;
fn advance(&mut self) -> Result<bool, Self::Error>;
fn current(&mut self) -> Result<T, Self::Error>;
fn with_current<'a, R>(
&'a mut self,
f: impl FnOnce(Self::Current<'a>) -> R,
) -> Result<R, Self::Error>;
}
pub trait Codec: Copy {
type Item;
type Error: std::error::Error + Send + Sync + 'static;
type Writer<W: Write>;
type Cursor<R: Read>: CodecCursor<Self::Item, Error = Self::Error>;
fn writer<W: Write>(&self, dest: W) -> Self::Writer<W>;
fn cursor<R: Read>(&self, source: R) -> Self::Cursor<R>;
}
pub trait KeyedCodecWriter<I: ?Sized, K> {
type Error: std::error::Error + Send + Sync + 'static;
fn write_keyed(&mut self, item: &I, key: &K) -> Result<(), Self::Error>;
fn finish(self) -> Result<(), Self::Error>;
}
pub trait KeyedCodecCursor<T, K>: CodecCursor<T> {
fn current_key(&self) -> Result<K, Self::Error>;
}
pub trait KeyedCodec: Codec {
type Key: Clone;
type KeyedWriter<W: Write>;
type KeyedCursor<R: Read>: KeyedCodecCursor<Self::Item, Self::Key, Error = Self::Error>;
fn keyed_writer<W: Write>(&self, dest: W) -> Self::KeyedWriter<W>;
fn keyed_cursor<R: Read>(&self, source: R) -> Self::KeyedCursor<R>;
}
pub trait DeriveKey<I: ?Sized>: KeyedCodec {
fn derive_key(&self, item: &I) -> Self::Key;
}
#[cfg(test)]
mod tests {
use std::io::BufWriter;
use super::*;
#[derive(Clone, Copy)]
struct U64Codec;
struct U64Writer<W: Write> {
inner: BufWriter<W>,
}
impl<W: Write> CodecWriter<u64> for U64Writer<W> {
type Error = std::io::Error;
fn write(&mut self, item: &u64) -> Result<(), Self::Error> {
use std::io::Write as _;
self.inner.write_all(&item.to_le_bytes())
}
fn finish(mut self) -> Result<(), Self::Error> {
use std::io::Write as _;
self.inner.flush()
}
}
struct U64Reader<R: Read> {
inner: R,
current: Option<u64>,
}
impl<R: Read> CodecCursor<u64> for U64Reader<R> {
type Error = std::io::Error;
type Current<'a>
= u64
where
Self: 'a;
fn advance(&mut self) -> Result<bool, Self::Error> {
let mut buf = [0u8; 8];
match self.inner.read(&mut buf[..1]) {
Ok(0) => {
self.current = None;
Ok(false)
}
Ok(_) => {
self.inner.read_exact(&mut buf[1..])?;
self.current = Some(u64::from_le_bytes(buf));
Ok(true)
}
Err(e) => Err(e),
}
}
fn current(&mut self) -> Result<u64, Self::Error> {
self.current
.ok_or_else(|| std::io::Error::other("current called before advance"))
}
fn with_current<'a, F>(
&'a mut self,
f: impl FnOnce(Self::Current<'a>) -> F,
) -> Result<F, Self::Error> {
self.current().map(f)
}
}
impl Codec for U64Codec {
type Item = u64;
type Error = std::io::Error;
type Writer<W: Write> = U64Writer<W>;
type Cursor<R: Read> = U64Reader<R>;
fn writer<W: Write>(&self, dest: W) -> U64Writer<W> {
U64Writer {
inner: BufWriter::new(dest),
}
}
fn cursor<R: Read>(&self, source: R) -> U64Reader<R> {
U64Reader {
inner: source,
current: None,
}
}
}
#[test]
fn codec_round_trips_single_item() {
let mut buf = Vec::new();
let mut writer = U64Codec.writer(&mut buf);
writer.write(&42u64).expect("write should succeed");
writer.finish().expect("finish should succeed");
assert_eq!(buf.len(), 8, "u64 should write exactly 8 bytes");
let mut reader = U64Codec.cursor(std::io::Cursor::new(&buf));
assert!(reader.advance().expect("advance should succeed"));
let visited = reader
.with_current(|item| item)
.expect("with_current should succeed");
assert_eq!(visited, 42, "current representation should match");
let item = reader.current().expect("current should succeed");
assert_eq!(item, 42, "round-tripped value should match");
}
#[test]
fn codec_round_trips_multiple_items() {
let values = vec![1u64, 2, 3, u64::MAX, 0];
let mut buf = Vec::new();
let mut writer = U64Codec.writer(&mut buf);
for v in &values {
writer.write(v).expect("write should succeed");
}
writer.finish().expect("finish should succeed");
let mut reader = U64Codec.cursor(std::io::Cursor::new(&buf));
let mut recovered = Vec::new();
while reader.advance().expect("advance should succeed") {
recovered.push(reader.current().expect("current should succeed"));
}
assert_eq!(
recovered, values,
"all round-tripped values should match in order"
);
}
#[test]
fn codec_read_empty_returns_none() {
let buf: Vec<u8> = Vec::new();
let mut reader = U64Codec.cursor(std::io::Cursor::new(&buf));
let result = reader.advance().expect("reading empty should not error");
assert!(!result, "reading from an empty source should return false");
}
#[test]
fn codec_read_truncated_returns_error() {
let buf = vec![0u8; 3]; let mut reader = U64Codec.cursor(std::io::Cursor::new(&buf));
let result = reader.advance();
assert!(
result.is_err(),
"reading a partial record should return an error, not None"
);
}
}