use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AnchorLimits {
max_value_chars: Option<usize>,
max_entries: Option<usize>,
}
impl AnchorLimits {
pub const UNBOUNDED: Self = Self {
max_value_chars: None,
max_entries: None,
};
#[must_use]
pub const fn bounded(max_value_chars: usize, max_entries: usize) -> Self {
Self {
max_value_chars: Some(max_value_chars),
max_entries: Some(max_entries),
}
}
}
#[derive(Debug)]
pub struct ScalarAnchorTable<M = ()> {
entries: HashMap<usize, (String, M)>,
limits: AnchorLimits,
}
impl<M> ScalarAnchorTable<M> {
#[must_use]
pub fn new(limits: AnchorLimits) -> Self {
Self {
entries: HashMap::new(),
limits,
}
}
pub fn record(&mut self, anchor_id: usize, text: &str, meta: M) {
if anchor_id == 0 {
return;
}
if let Some(max_chars) = self.limits.max_value_chars
&& text.chars().take(max_chars.saturating_add(1)).count() > max_chars
{
return;
}
if let Some(max_entries) = self.limits.max_entries
&& self.entries.len() >= max_entries
&& !self.entries.contains_key(&anchor_id)
{
return;
}
self.entries.insert(anchor_id, (text.to_string(), meta));
}
#[must_use]
pub fn get(&self, anchor_id: usize) -> Option<(&str, &M)> {
self.entries
.get(&anchor_id)
.map(|(text, meta)| (text.as_str(), meta))
}
}
#[cfg(test)]
mod tests {
use super::{AnchorLimits, ScalarAnchorTable};
#[test]
fn test_anchor_id_zero_is_a_no_op() {
let mut table: ScalarAnchorTable = ScalarAnchorTable::new(AnchorLimits::UNBOUNDED);
table.record(0, "ignored", ());
assert_eq!(table.get(0), None);
}
#[test]
fn test_char_count_at_boundary_is_recorded_over_boundary_is_skipped() {
let mut table: ScalarAnchorTable = ScalarAnchorTable::new(AnchorLimits::bounded(4, 10));
table.record(1, "abcd", ()); table.record(2, "abcde", ()); assert_eq!(table.get(1), Some(("abcd", &())));
assert_eq!(table.get(2), None);
}
#[test]
fn test_entry_cap_skips_new_id_but_allows_overwriting_existing_id() {
let mut table: ScalarAnchorTable = ScalarAnchorTable::new(AnchorLimits::bounded(64, 1));
table.record(1, "first", ());
table.record(2, "second", ()); assert_eq!(table.get(2), None);
table.record(1, "overwritten", ()); assert_eq!(table.get(1), Some(("overwritten", &())));
}
#[test]
fn test_unbounded_limits_record_past_both_bounds() {
let mut table: ScalarAnchorTable = ScalarAnchorTable::new(AnchorLimits::UNBOUNDED);
let long = "v".repeat(10_000);
for id in 1..=1000usize {
table.record(id, &long, ());
}
assert_eq!(table.get(1).map(|(text, ())| text.len()), Some(10_000));
assert_eq!(table.get(1000).map(|(text, ())| text.len()), Some(10_000));
}
}