use std::collections::VecDeque;
use super::Message;
pub const DEFAULT_ERROR_LOG_CAPACITY: usize = 32;
#[derive(Debug, Clone)]
pub struct ErrorLog {
entries: VecDeque<Message>,
max: usize,
cursor: Option<usize>,
}
impl Default for ErrorLog {
fn default() -> Self {
Self::with_capacity(DEFAULT_ERROR_LOG_CAPACITY)
}
}
impl ErrorLog {
#[must_use]
pub fn with_capacity(max: usize) -> Self {
Self {
entries: VecDeque::with_capacity(max.max(1)),
max: max.max(1),
cursor: None,
}
}
pub fn push(&mut self, msg: Message) {
if self.entries.len() == self.max {
self.entries.pop_front();
}
self.entries.push_back(msg);
self.cursor = None;
}
#[must_use]
#[allow(dead_code)] pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
#[allow(dead_code)] pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
#[allow(dead_code)] pub fn capacity(&self) -> usize {
self.max
}
#[allow(dead_code)] pub fn iter_newest_first(&self) -> impl Iterator<Item = &Message> {
self.entries.iter().rev()
}
pub fn next_recall(&mut self) -> Option<&Message> {
if self.entries.is_empty() {
return None;
}
let last_idx = self.entries.len() - 1;
let next = match self.cursor {
None => last_idx,
Some(0) => last_idx, Some(n) => n - 1,
};
self.cursor = Some(next);
self.entries.get(next)
}
}
#[cfg(test)]
mod tests {
use super::super::MessageType;
use super::*;
fn err(s: &str) -> Message {
Message {
content: s.to_string(),
message_type: MessageType::Error,
}
}
#[test]
fn new_log_is_empty() {
let log = ErrorLog::default();
assert!(log.is_empty());
assert_eq!(log.len(), 0);
assert_eq!(log.capacity(), DEFAULT_ERROR_LOG_CAPACITY);
}
#[test]
fn push_appends_and_len_tracks() {
let mut log = ErrorLog::with_capacity(4);
log.push(err("a"));
log.push(err("b"));
assert_eq!(log.len(), 2);
}
#[test]
fn push_evicts_oldest_when_full() {
let mut log = ErrorLog::with_capacity(2);
log.push(err("a"));
log.push(err("b"));
log.push(err("c"));
assert_eq!(log.len(), 2);
let bodies: Vec<&str> = log
.iter_newest_first()
.map(|m| m.content.as_str())
.collect();
assert_eq!(bodies, vec!["c", "b"], "oldest evicted; order newest-first");
}
#[test]
fn capacity_zero_is_promoted_to_one() {
let mut log = ErrorLog::with_capacity(0);
assert_eq!(log.capacity(), 1);
log.push(err("only"));
assert_eq!(log.len(), 1);
}
#[test]
fn iter_newest_first_reverses_insertion_order() {
let mut log = ErrorLog::with_capacity(3);
log.push(err("a"));
log.push(err("b"));
log.push(err("c"));
let bodies: Vec<&str> = log
.iter_newest_first()
.map(|m| m.content.as_str())
.collect();
assert_eq!(bodies, vec!["c", "b", "a"]);
}
#[test]
fn next_recall_walks_newest_then_older() {
let mut log = ErrorLog::with_capacity(3);
log.push(err("oldest"));
log.push(err("middle"));
log.push(err("newest"));
assert_eq!(log.next_recall().unwrap().content, "newest");
assert_eq!(log.next_recall().unwrap().content, "middle");
assert_eq!(log.next_recall().unwrap().content, "oldest");
assert_eq!(log.next_recall().unwrap().content, "newest");
}
#[test]
fn next_recall_returns_none_when_empty() {
let mut log = ErrorLog::default();
assert!(log.next_recall().is_none());
}
#[test]
fn push_resets_recall_cursor() {
let mut log = ErrorLog::with_capacity(4);
log.push(err("a"));
log.push(err("b"));
assert_eq!(log.next_recall().unwrap().content, "b");
assert_eq!(log.next_recall().unwrap().content, "a");
log.push(err("c"));
assert_eq!(log.next_recall().unwrap().content, "c");
}
}