use std::error::Error;
use std::fmt;
use std::num::NonZeroU32;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ChunkSize(NonZeroU32);
impl ChunkSize {
pub fn new(value: u32) -> Result<Self, ChunkError> {
NonZeroU32::new(value).map(Self).ok_or(ChunkError::ZeroSize)
}
#[must_use]
pub const fn get(self) -> u32 {
self.0.get()
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ChunkCount(u64);
impl ChunkCount {
pub const ZERO: Self = Self(0);
#[must_use]
pub const fn new(value: u64) -> Self {
Self(value)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
pub fn checked_add(self, other: Self) -> Result<Self, ChunkError> {
self.0
.checked_add(other.0)
.map(Self)
.ok_or(ChunkError::CountOverflow)
}
pub fn checked_increment(self) -> Result<Self, ChunkError> {
self.checked_add(Self(1))
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ChunkCounts {
read: ChunkCount,
processed: ChunkCount,
written: ChunkCount,
filtered: ChunkCount,
}
impl ChunkCounts {
pub fn new(
read: ChunkCount,
processed: ChunkCount,
written: ChunkCount,
filtered: ChunkCount,
) -> Result<Self, ChunkError> {
let classified = processed.checked_add(filtered)?;
if classified > read {
return Err(ChunkError::ClassifiedExceedsRead);
}
if written > processed {
return Err(ChunkError::WrittenExceedsProcessed);
}
Ok(Self {
read,
processed,
written,
filtered,
})
}
#[must_use]
pub const fn read(self) -> ChunkCount {
self.read
}
#[must_use]
pub const fn processed(self) -> ChunkCount {
self.processed
}
#[must_use]
pub const fn written(self) -> ChunkCount {
self.written
}
#[must_use]
pub const fn filtered(self) -> ChunkCount {
self.filtered
}
pub fn checked_add(self, other: Self) -> Result<Self, ChunkError> {
Self::new(
self.read.checked_add(other.read)?,
self.processed.checked_add(other.processed)?,
self.written.checked_add(other.written)?,
self.filtered.checked_add(other.filtered)?,
)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ChunkProgress {
size: ChunkSize,
counts: ChunkCounts,
}
impl ChunkProgress {
#[must_use]
pub const fn new(size: ChunkSize) -> Self {
Self {
size,
counts: ChunkCounts {
read: ChunkCount::ZERO,
processed: ChunkCount::ZERO,
written: ChunkCount::ZERO,
filtered: ChunkCount::ZERO,
},
}
}
pub fn from_counts(size: ChunkSize, counts: ChunkCounts) -> Result<Self, ChunkError> {
if counts.read().get() > u64::from(size.get()) {
return Err(ChunkError::SizeExceeded);
}
Ok(Self { size, counts })
}
#[must_use]
pub const fn size(self) -> ChunkSize {
self.size
}
#[must_use]
pub const fn counts(self) -> ChunkCounts {
self.counts
}
#[must_use]
pub fn is_full(self) -> bool {
self.counts.read().get() == u64::from(self.size.get())
}
pub fn record_read(&mut self) -> Result<(), ChunkError> {
if self.is_full() {
return Err(ChunkError::SizeExceeded);
}
self.counts.read = self.counts.read.checked_increment()?;
Ok(())
}
pub fn record_processed(&mut self) -> Result<(), ChunkError> {
let next = self.counts.processed.checked_increment()?;
let classified = next.checked_add(self.counts.filtered)?;
if classified > self.counts.read {
return Err(ChunkError::ClassifiedExceedsRead);
}
self.counts.processed = next;
Ok(())
}
pub fn record_filtered(&mut self) -> Result<(), ChunkError> {
let next = self.counts.filtered.checked_increment()?;
let classified = self.counts.processed.checked_add(next)?;
if classified > self.counts.read {
return Err(ChunkError::ClassifiedExceedsRead);
}
self.counts.filtered = next;
Ok(())
}
pub fn record_written(&mut self, count: ChunkCount) -> Result<(), ChunkError> {
let next = self.counts.written.checked_add(count)?;
if next > self.counts.processed {
return Err(ChunkError::WrittenExceedsProcessed);
}
self.counts.written = next;
Ok(())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum ChunkError {
ZeroSize,
CountOverflow,
ClassifiedExceedsRead,
WrittenExceedsProcessed,
SizeExceeded,
}
impl fmt::Display for ChunkError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::ZeroSize => "chunk size must be nonzero",
Self::CountOverflow => "chunk count arithmetic overflowed",
Self::ClassifiedExceedsRead => "processed and filtered counts exceed the read count",
Self::WrittenExceedsProcessed => "written count exceeds the processed count",
Self::SizeExceeded => "read count exceeds the configured chunk size",
})
}
}
impl Error for ChunkError {}