use std::time::SystemTime;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyValue {
pub key: String,
pub value: Vec<u8>,
}
impl KeyValue {
pub fn new(key: impl Into<String>, value: impl Into<Vec<u8>>) -> Self {
Self {
key: key.into(),
value: value.into(),
}
}
pub fn value_str(&self) -> Option<&str> {
std::str::from_utf8(&self.value).ok()
}
}
#[derive(Debug, Clone)]
pub struct Entry {
pub key: String,
pub value: Vec<u8>,
pub expires_at: Option<SystemTime>,
pub created_at: SystemTime,
pub updated_at: SystemTime,
}
impl Entry {
pub fn is_expired(&self) -> bool {
self.expires_at
.map(|exp| exp < SystemTime::now())
.unwrap_or(false)
}
pub fn ttl(&self) -> Option<std::time::Duration> {
self.expires_at
.and_then(|exp| exp.duration_since(SystemTime::now()).ok())
}
pub fn value_str(&self) -> Option<&str> {
std::str::from_utf8(&self.value).ok()
}
}
#[derive(Debug, Clone, Default)]
pub struct Stats {
pub total_keys: u64,
pub expired_keys: u64,
pub total_value_bytes: u64,
pub avg_value_bytes: f64,
pub max_value_bytes: u64,
pub table_size_bytes: u64,
pub index_size_bytes: u64,
}
#[derive(Debug, Clone, Default)]
pub struct ScanOptions {
pub prefix: Option<String>,
pub limit: Option<usize>,
pub offset: Option<usize>,
pub include_expired: bool,
}
impl ScanOptions {
pub fn new() -> Self {
Self::default()
}
pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = Some(prefix.into());
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
pub fn include_expired(mut self, include: bool) -> Self {
self.include_expired = include;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CasResult {
Success,
Mismatch {
current: Option<Vec<u8>>,
},
NotFound,
}
impl CasResult {
#[inline]
pub fn is_success(&self) -> bool {
matches!(self, CasResult::Success)
}
#[inline]
pub fn is_mismatch(&self) -> bool {
matches!(self, CasResult::Mismatch { .. })
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_key_value() {
let kv = KeyValue::new("test", b"value".to_vec());
assert_eq!(kv.key, "test");
assert_eq!(kv.value, b"value");
assert_eq!(kv.value_str(), Some("value"));
let kv_binary = KeyValue::new("binary", vec![0xff, 0xfe]);
assert!(kv_binary.value_str().is_none());
}
#[test]
fn test_entry_expiration() {
let now = SystemTime::now();
let entry = Entry {
key: "test".into(),
value: vec![],
expires_at: None,
created_at: now,
updated_at: now,
};
assert!(!entry.is_expired());
assert!(entry.ttl().is_none());
let entry = Entry {
key: "test".into(),
value: vec![],
expires_at: Some(now - Duration::from_secs(1)),
created_at: now,
updated_at: now,
};
assert!(entry.is_expired());
}
#[test]
fn test_scan_options_builder() {
let opts = ScanOptions::new()
.prefix("user:")
.limit(100)
.offset(50)
.include_expired(true);
assert_eq!(opts.prefix, Some("user:".into()));
assert_eq!(opts.limit, Some(100));
assert_eq!(opts.offset, Some(50));
assert!(opts.include_expired);
}
#[test]
fn test_cas_result() {
assert!(CasResult::Success.is_success());
assert!(!CasResult::Success.is_mismatch());
let mismatch = CasResult::Mismatch {
current: Some(vec![1, 2, 3]),
};
assert!(!mismatch.is_success());
assert!(mismatch.is_mismatch());
}
}