blob: a2e61d22bda0441d4ec6dbbd9ab5448a239e2553 [file] [log] [blame]
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
//
// you may not use this file except in compliance with the License.
//
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
//
// distributed under the License is distributed on an "AS IS" BASIS,
//
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
//
// limitations under the License.
//! A collection that tracks all previous entries that have been inserted and removed and provides a nice-looking output when displayed.
use chrono::{DateTime, Utc};
use prettytable::{Cell, Row, Table};
use std::{collections::HashMap, fmt::Debug};
use thiserror::Error;
/// The insertion for this key-value pair failed.
#[derive(Error, Debug)]
#[error("Key {:?} already exists. Cannot insert value {:?}. Key was originally inserted at {}", key, value, insertion_time.to_rfc3339())]
pub struct InsertError<K: Debug, V: Debug> {
pub key: K,
pub value: V,
insertion_time: DateTime<Utc>,
}
/// The update operation for this key-value pair failed.
#[derive(Error, Debug)]
#[error("Key '{:?}' does not exist. Cannot update value {:?}.", key, value)]
pub struct UpdateError<K: Debug, V: Debug> {
pub key: K,
pub value: V,
}
/// The removal operation for this key failed.
#[derive(Error, Debug)]
pub enum RemoveError<K: Debug, V: Debug> {
#[error(
"Key '{:?}' does not exist. Key was originally removed at time {:?} with value {:?}",
key,
old_value,
removal_time.to_rfc3339()
)]
KeyAlreadyRemoved {
key: K,
old_value: V,
removal_time: DateTime<Utc>,
},
#[error("Key '{:?}' does not exist", key)]
KeyDoesNotExist { key: K },
}
/// The get operation for this key failed.
#[derive(Error, Debug)]
pub enum GetError<K: Debug, V: Debug> {
#[error(
"Key '{:?}' does not exist. Key was originally removed at time {:?} with value {:?}",
key,
old_value,
removal_time.to_rfc3339()
)]
KeyAlreadyRemoved {
key: K,
old_value: V,
removal_time: DateTime<Utc>,
},
#[error("Key '{:?}' does not exist", key)]
KeyDoesNotExist { key: K },
}
#[derive(Clone, Debug)]
struct Session<K, V> {
key: K,
value: V,
insertion_time: DateTime<Utc>,
last_update_time: DateTime<Utc>,
removal_time: Option<DateTime<Utc>>,
}
impl<K, V> Session<K, V> {
fn new(key: K, value: V) -> Self {
let now = Utc::now();
Self {
key,
value,
insertion_time: now,
last_update_time: now,
removal_time: None,
}
}
fn set_value(&mut self, value: V) -> V {
let old_value = std::mem::replace(&mut self.value, value);
self.update_last_updated_time();
old_value
}
fn update_last_updated_time(&mut self) {
self.last_update_time = Utc::now();
}
fn update_removal_time(&mut self) {
self.update_last_updated_time();
self.removal_time = Some(Utc::now());
}
fn get_duration(&self) -> i64 {
let end_time = self.removal_time.unwrap_or_else(Utc::now);
end_time.timestamp_millis() - self.insertion_time.timestamp_millis()
}
}
enum EventStatus {
Active,
Complete,
}
impl std::fmt::Display for EventStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EventStatus::Active => write!(f, "ACTIVE"),
EventStatus::Complete => write!(f, "COMPLETE"),
}
}
}
/// A map that tracks when keys were added and removed.
#[derive(Clone)]
pub struct CollectionHistory<K, V> {
max_history: usize,
past_sessions: Vec<Session<K, V>>,
active_sessions: HashMap<K, Session<K, V>>,
}
impl<K, V> CollectionHistory<K, V>
where
K: Eq + std::hash::Hash + Clone + std::fmt::Debug,
V: std::fmt::Debug + Clone,
{
/// Creates a new [CollectionHistory] with a maximum number of `max_history` historical entries tracked.
pub fn new(max_history: usize) -> Self {
Self {
max_history,
past_sessions: Vec::new(),
active_sessions: HashMap::new(),
}
}
/// Inserts a key-value pair. If the key already exists, an InsertError with the key and value is returned.
pub fn insert(&mut self, key: K, value: V) -> Result<(), InsertError<K, V>> {
if let Some(session) = self.active_sessions.get(&key) {
return Err(InsertError {
key,
value,
insertion_time: session.insertion_time,
});
}
self.active_sessions
.insert(key.clone(), Session::new(key, value));
self.prune_old_sessions();
Ok(())
}
/// Updates the key to the new value, returning the old value if successful.
pub fn update(&mut self, key: K, value: V) -> Result<V, UpdateError<K, V>> {
if let Some(session) = self.active_sessions.get_mut(&key) {
return Ok(session.set_value(value));
}
Err(UpdateError { key, value })
}
/// Removes the value corresponding to key, if it exists.
pub fn remove(&mut self, key: K) -> Result<V, RemoveError<K, V>> {
if let Some(mut session) = self.active_sessions.remove(&key) {
session.update_removal_time();
let value = session.value.clone();
self.past_sessions.insert(0, session);
self.prune_old_sessions();
Ok(value)
} else if let Some(session) = self.find_old_session(&key) {
return Err(RemoveError::KeyAlreadyRemoved {
key,
old_value: session.value.clone(),
removal_time: session.last_update_time,
});
} else {
return Err(RemoveError::KeyDoesNotExist { key });
}
}
/// Returns true if key is currently active.
pub fn contains_key(&self, key: &K) -> bool {
self.active_sessions.contains_key(key)
}
/// Returns the value corresponding to `key` if it exists.
pub fn get(&self, key: &K) -> Result<&V, GetError<K, &V>> {
if let Some(session) = self.active_sessions.get(key) {
Ok(&session.value)
} else if let Some(session) = self.find_old_session(key) {
Err(GetError::KeyAlreadyRemoved {
key: key.clone(),
old_value: &session.value,
removal_time: session.last_update_time,
})
} else {
Err(GetError::KeyDoesNotExist { key: key.clone() })
}
}
/// Clears this [CollectionHistory].
pub fn clear(&mut self) {
let keys: Vec<K> = self.active_sessions.keys().cloned().collect();
for key in keys {
let _ = self.remove(key);
}
}
/// Returns an iterator over the active keys in this [CollectionHistory].
pub fn keys(&self) -> impl Iterator<Item = &K> {
self.active_sessions.keys()
}
/// Returns an iterator over the active values in this [CollectionHistory].
pub fn values(&self) -> impl Iterator<Item = &V> {
self.active_sessions.values().map(|s| &s.value)
}
/// Returns an iterator over the active entries in this [CollectionHistory].
pub fn entry_set(&self) -> impl Iterator<Item = (&K, &V)> {
self.active_sessions.iter().map(|(k, s)| (k, &s.value))
}
/// Returns the number of entries in this [CollectionHistory].
pub fn len(&self) -> usize {
self.active_sessions.len()
}
/// Returns true if there are no active sessions in this [CollectionHistory].
pub fn is_empty(&self) -> bool {
self.active_sessions.is_empty()
}
fn history_size(&self) -> usize {
self.past_sessions.len() + self.len()
}
fn prune_old_sessions(&mut self) {
if self.active_sessions.len() >= self.max_history {
self.past_sessions.clear();
return;
}
while self.history_size() > self.max_history {
self.past_sessions.remove(0);
}
}
fn find_old_session(&self, key: &K) -> Option<&Session<K, V>> {
self.past_sessions.iter().find(|s| &s.key == key)
}
}
impl<K: Debug, V: Debug> std::fmt::Display for CollectionHistory<K, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut sorted_sessions: Vec<&Session<K, V>> = self.active_sessions.values().collect();
sorted_sessions.sort_by(|a, b| b.last_update_time.cmp(&a.last_update_time));
let mut table = Table::new();
table.add_row(Row::new(vec![
Cell::new("Key"),
Cell::new("Value"),
Cell::new("Status"),
Cell::new("Start Time"),
Cell::new("End Time"),
Cell::new("Duration"),
]));
for session in sorted_sessions {
table.add_row(Row::new(vec![
Cell::new(&format!("{:?}", &session.key)),
Cell::new(&format!("{:?}", &session.value)),
Cell::new(&EventStatus::Active.to_string()),
Cell::new(&session.insertion_time.to_rfc3339()),
Cell::new(&session.last_update_time.to_rfc3339()),
Cell::new(&format!("{}ms", session.get_duration())),
]));
}
for session in &self.past_sessions {
table.add_row(Row::new(vec![
Cell::new(&format!("{:?}", &session.key)),
Cell::new(&format!("{:?}", &session.value)),
Cell::new(&EventStatus::Complete.to_string()),
Cell::new(&session.insertion_time.to_rfc3339()),
Cell::new(&session.last_update_time.to_rfc3339()),
Cell::new(&format!("{}ms", session.get_duration())),
]));
}
write!(f, "{table}")
}
}