use super::*;
#[derive(Clone, Copy, Debug)]
pub enum EntryStatus {
Constant,
ExpiresAtSeconds(u64),
}
impl EntryStatus {
#[inline(always)]
fn new(expires_at: Option<u64>) -> Self {
match expires_at {
Some(t) => Self::ExpiresAtSeconds(t),
None => Self::Constant,
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct ExpirableEntry<V> {
value: V,
status: EntryStatus,
}
impl<V> ExpirableEntry<V> {
#[inline(always)]
pub(crate) fn new(v: V, expires_at: Option<u64>) -> Self {
Self {
value: v,
status: EntryStatus::new(expires_at),
}
}
#[inline(always)]
pub(crate) fn status(&self) -> &EntryStatus {
&self.status
}
#[inline(always)]
pub(crate) fn value(&self) -> &V {
&self.value
}
#[inline(always)]
pub(crate) fn value_mut(&mut self) -> &mut V {
&mut self.value
}
#[inline(always)]
pub(crate) fn owned_value(self) -> V {
self.value
}
#[inline(always)]
pub(crate) fn is_expired(&self, now_seconds: u64) -> bool {
match self.status {
EntryStatus::Constant => false,
EntryStatus::ExpiresAtSeconds(expires_at_seconds) => now_seconds >= expires_at_seconds,
}
}
#[inline(always)]
pub(crate) fn remaining_duration(&self, now_seconds: u64) -> Option<Duration> {
match self.status {
EntryStatus::Constant => None,
EntryStatus::ExpiresAtSeconds(expires_at_seconds) => Some(Duration::from_secs(
expires_at_seconds.saturating_sub(now_seconds),
)),
}
}
pub(crate) fn update_status(&mut self, status: EntryStatus) {
self.status = status;
}
}
#[cfg(feature = "serde")]
impl<V: serde::Serialize> serde::Serialize for ExpirableEntry<V> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.value.serialize(serializer)
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockClock {
current_time: u64,
}
impl Clock for MockClock {
fn elapsed_seconds_since_creation(&self) -> u64 {
self.current_time
}
}
#[test]
fn test_entry_status() {
let clock = MockClock { current_time: 1000 };
let entry_status = EntryStatus::new(None);
assert!(matches!(entry_status, EntryStatus::Constant));
let duration = Duration::from_secs(60);
let entry_status = EntryStatus::new(Some(
clock.elapsed_seconds_since_creation() + duration.as_secs(),
));
assert!(matches!(entry_status, EntryStatus::ExpiresAtSeconds(1060)));
}
#[test]
fn test_constant_entry() {
let clock = MockClock { current_time: 1000 };
let entry = ExpirableEntry::new("constant value", None);
assert_eq!(entry.value(), &"constant value");
assert!(!entry.is_expired(clock.elapsed_seconds_since_creation()));
assert!(matches!(entry.status(), EntryStatus::Constant));
}
#[test]
fn test_expirable_entry() {
let clock = MockClock { current_time: 1000 };
let duration = Duration::from_secs(60);
let entry = ExpirableEntry::new(
"expirable value",
Some(clock.elapsed_seconds_since_creation() + duration.as_secs()),
);
assert_eq!(entry.value(), &"expirable value");
assert!(!entry.is_expired(clock.elapsed_seconds_since_creation()));
assert!(matches!(
entry.status(),
EntryStatus::ExpiresAtSeconds(1060)
));
}
#[test]
fn test_expirable_entry_is_expired() {
let clock = MockClock { current_time: 1000 };
let duration = Duration::from_secs(60);
let entry = ExpirableEntry::new(
"expirable value",
Some(clock.elapsed_seconds_since_creation() + duration.as_secs()),
);
assert!(!entry.is_expired(clock.elapsed_seconds_since_creation()));
let clock = MockClock { current_time: 1070 };
assert!(entry.is_expired(clock.elapsed_seconds_since_creation()));
}
#[test]
fn test_remaining_duration_for_expires_at_seconds() {
let clock = MockClock { current_time: 1000 };
let duration = Duration::from_secs(60);
let entry = ExpirableEntry::new(
"expirable value",
Some(clock.elapsed_seconds_since_creation() + duration.as_secs()),
);
assert!(!entry.is_expired(clock.elapsed_seconds_since_creation()));
assert_eq!(
entry.remaining_duration(clock.elapsed_seconds_since_creation()),
Some(Duration::from_secs(60))
);
let clock = MockClock { current_time: 1050 };
assert!(!entry.is_expired(clock.elapsed_seconds_since_creation()));
assert_eq!(
entry.remaining_duration(clock.elapsed_seconds_since_creation()),
Some(Duration::from_secs(10))
);
let clock = MockClock { current_time: 1070 };
assert!(entry.is_expired(clock.elapsed_seconds_since_creation()));
assert_eq!(
entry.remaining_duration(clock.elapsed_seconds_since_creation()),
Some(Duration::from_secs(0))
);
}
#[test]
fn test_remaining_duration_for_constant() {
let clock = MockClock { current_time: 1000 };
let entry = ExpirableEntry::new("constant value", None);
assert_eq!(
entry.remaining_duration(clock.elapsed_seconds_since_creation()),
None
);
}
}