use alloc::collections::VecDeque;
use core::fmt;
#[cfg(test)]
mod tests;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct HistoryLimits {
pub entries: usize,
pub payload_bytes: usize,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct HistoryAppend {
pub evicted: usize,
pub evicted_payload_bytes: usize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum HistoryRejectionReason {
Disabled,
PayloadTooLarge {
supplied: usize,
maximum: usize,
},
}
impl fmt::Display for HistoryRejectionReason {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Disabled => formatter.write_str("history admission is disabled"),
Self::PayloadTooLarge { supplied, maximum } => write!(
formatter,
"history payload charge {supplied} exceeds {maximum}"
),
}
}
}
impl core::error::Error for HistoryRejectionReason {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HistoryRejection<T> {
pub value: T,
pub reason: HistoryRejectionReason,
}
impl<T> fmt::Display for HistoryRejection<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.reason.fmt(formatter)
}
}
impl<T: fmt::Debug> core::error::Error for HistoryRejection<T> {}
#[derive(Clone, Debug)]
struct Row<T> {
value: T,
payload_bytes: usize,
}
#[derive(Clone, Debug)]
pub struct BoundedHistory<T> {
limits: HistoryLimits,
rows: VecDeque<Row<T>>,
payload_bytes: usize,
}
impl<T> BoundedHistory<T> {
#[must_use]
pub const fn new(limits: HistoryLimits) -> Self {
Self {
limits,
rows: VecDeque::new(),
payload_bytes: 0,
}
}
pub fn append(
&mut self,
value: T,
payload_bytes: usize,
) -> Result<HistoryAppend, HistoryRejection<T>> {
let reason = if self.limits.entries == 0 {
Some(HistoryRejectionReason::Disabled)
} else if payload_bytes > self.limits.payload_bytes {
Some(HistoryRejectionReason::PayloadTooLarge {
supplied: payload_bytes,
maximum: self.limits.payload_bytes,
})
} else {
None
};
if let Some(reason) = reason {
return Err(HistoryRejection { value, reason });
}
let mut receipt = HistoryAppend::default();
while self.rows.len() >= self.limits.entries
|| self.payload_bytes > self.limits.payload_bytes - payload_bytes
{
if let Some(row) = self.rows.pop_front() {
self.payload_bytes -= row.payload_bytes;
receipt.evicted += 1;
receipt.evicted_payload_bytes += row.payload_bytes;
}
}
self.rows.push_back(Row {
value,
payload_bytes,
});
self.payload_bytes += payload_bytes;
Ok(receipt)
}
pub fn entries(&self) -> impl DoubleEndedIterator<Item = &T> + ExactSizeIterator + '_ {
self.rows.iter().map(|row| &row.value)
}
#[must_use]
pub const fn retained_payload_bytes(&self) -> usize {
self.payload_bytes
}
#[must_use]
pub const fn limits(&self) -> HistoryLimits {
self.limits
}
pub fn clear(&mut self) {
self.rows.clear();
self.payload_bytes = 0;
}
}