use super::CodecError;
use crate::{Offset, layout::Fixed};
pub const OUTCOME_RUN_BYTES: usize = 13;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppendOutcome {
pub disposition: AppendDisposition,
pub offset: Offset,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
#[repr(u8)]
pub enum AppendDisposition {
Stored = 0,
Duplicate = 1,
}
impl TryFrom<u8> for AppendDisposition {
type Error = CodecError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Stored),
1 => Ok(Self::Duplicate),
_ => Err(CodecError::Profile),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OutcomeRun {
pub disposition: AppendDisposition,
pub first_offset: Offset,
pub records: u32,
}
impl OutcomeRun {
pub fn outcome(self, index: u32) -> Option<AppendOutcome> {
(index < self.records)
.then(|| self.first_offset.get().checked_add(u64::from(index)))
.flatten()
.map(|offset| AppendOutcome {
disposition: self.disposition,
offset: Offset::new(offset),
})
}
pub fn outcomes(self) -> impl Iterator<Item = AppendOutcome> + Clone {
let first = self.first_offset.get();
let end = first.saturating_add(u64::from(self.records));
(first..end).map(move |offset| AppendOutcome {
disposition: self.disposition,
offset: Offset::new(offset),
})
}
pub fn encode(self) -> Result<[u8; OUTCOME_RUN_BYTES], CodecError> {
self.validate()?;
let mut bytes = [0; OUTCOME_RUN_BYTES];
bytes[0] = self.disposition as u8;
bytes[1..9].copy_from_slice(&self.first_offset.get().to_be_bytes());
bytes[9..].copy_from_slice(&self.records.to_be_bytes());
Ok(bytes)
}
pub fn decode(bytes: &[u8; OUTCOME_RUN_BYTES]) -> Result<Self, CodecError> {
let run = Self {
disposition: AppendDisposition::try_from(bytes.u8_at::<0>())?,
first_offset: Offset::new(bytes.u64_at::<1>()),
records: bytes.u32_at::<9>(),
};
run.validate()?;
Ok(run)
}
fn validate(self) -> Result<(), CodecError> {
if self.records == 0
|| self
.first_offset
.get()
.checked_add(u64::from(self.records))
.is_none()
{
return Err(CodecError::Length);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct OutcomeRuns<'a> {
remaining: &'a [AppendOutcome],
}
impl<'a> OutcomeRuns<'a> {
pub const fn new(outcomes: &'a [AppendOutcome]) -> Self {
Self {
remaining: outcomes,
}
}
}
impl Iterator for OutcomeRuns<'_> {
type Item = OutcomeRun;
fn next(&mut self) -> Option<Self::Item> {
let (&first, rest) = self.remaining.split_first()?;
let mut run = OutcomeRun {
disposition: first.disposition,
first_offset: first.offset,
records: 1,
};
for outcome in rest {
let Some(records) = run.records.checked_add(1) else {
break;
};
if outcome.disposition != run.disposition
|| run.first_offset.get().checked_add(u64::from(run.records))
!= Some(outcome.offset.get())
{
break;
}
run.records = records;
}
self.remaining = self
.remaining
.get(run.records as usize..)
.unwrap_or_default();
Some(run)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(
usize::from(!self.remaining.is_empty()),
Some(self.remaining.len()),
)
}
}
impl std::iter::FusedIterator for OutcomeRuns<'_> {}
#[cfg(test)]
mod tests;