use std::{ collections::{ HashMap, }, iter::FromIterator, sync::{ Mutex, MutexGuard, PoisonError, }, }; pub struct Store { status_dirs: HashMap , StatusKeyDirectory>, } #[derive (thiserror::Error, Debug, PartialEq)] pub enum Error { #[error ("key too long")] KeyTooLong, #[error ("mutex poisoned")] MutexPoisoned, #[error ("no such key dir")] NoSuchKeyDir, #[error ("value too long")] ValueTooLong, } pub struct StatusQuotas { pub max_keys: usize, pub max_key_bytes: usize, pub max_value_bytes: usize, pub max_payload_bytes: usize, } pub struct GetAfter { pub tuples: Vec <(Vec , Vec )>, pub sequence: u64, } impl Store { pub fn new (status_dirs: I) -> Self where I: Iterator , StatusQuotas)> { let status_dirs = HashMap::from_iter ( status_dirs .map (|(name, quotas)| (name, StatusKeyDirectory::new (quotas))) ); Self { status_dirs, } } pub fn list_key_dirs (&self) -> Vec > { self.status_dirs.iter () .map (|(k, _)| k.clone ()) .collect () } pub fn set (&self, name: &[u8], key: Vec , value: Vec ) -> Result <(), Error> { let dir = self.status_dirs.get (name) .ok_or (Error::NoSuchKeyDir)?; dir.set (key, value) } pub fn get_after (&self, name: &[u8], thresh: Option ) -> Result { let dir = self.status_dirs.get (name) .ok_or (Error::NoSuchKeyDir)?; dir.get_after (thresh) } } // End of public interface impl From >> for Error { fn from (_: PoisonError >) -> Self { Self::MutexPoisoned } } enum StoreCommand { SetStatus (SetStatusCommand), Multi (Vec ), } struct StatusKeyDirectory { quotas: StatusQuotas, // TODO: Make this tokio::sync::Mutex. table: Mutex , } #[derive (Default)] struct StatusTable { map: HashMap , StatusValue>, sequence: u64, } struct StatusValue { value: Vec , sequence: u64, } struct SetStatusCommand { key_dir: Vec , key: Vec , value: Vec , } impl StatusKeyDirectory { fn new (quotas: StatusQuotas) -> Self { Self { quotas, table: Mutex::new (Default::default ()), } } fn set (&self, key: Vec , value: Vec ) -> Result <(), Error> { if key.len () > self.quotas.max_key_bytes { return Err (Error::KeyTooLong); } if value.len () > self.quotas.max_value_bytes { return Err (Error::ValueTooLong); } { let mut guard = self.table.lock ()?; guard.set (&self.quotas, key, value); } Ok (()) } fn get_after (&self, thresh: Option ) -> Result { let guard = self.table.lock ()?; Ok (guard.get_after (thresh)) } } impl StatusTable { fn payload_bytes (&self) -> usize { self.map.iter () .map (|(k, v)| k.len () + v.len ()) .sum () } fn set (&mut self, quotas: &StatusQuotas, key: Vec , value: Vec ) { self.sequence += 1; if self.map.len () > quotas.max_keys { self.map.clear (); } let new_bytes = key.len () + value.len (); if self.payload_bytes () + new_bytes > quotas.max_payload_bytes { self.map.clear (); } let value = StatusValue { value, sequence: self.sequence, }; self.map.insert (key, value); } fn get_after (&self, thresh: Option ) -> GetAfter { let thresh = thresh.unwrap_or (0); let tuples = self.map.iter () .filter_map (|(key, value)| { if value.sequence <= thresh { return None; } Some ((key.clone (), value.value.clone ())) }) .collect (); GetAfter { tuples, sequence: self.sequence, } } } impl StatusValue { fn len (&self) -> usize { self.value.len () } } fn main () { println! ("Hello, world!"); } #[cfg (test)] mod tests { use super::*; fn get_after_eq (a: &GetAfter, b: &GetAfter) { assert_eq! (a.sequence, b.sequence); let a = a.tuples.clone (); let b = b.tuples.clone (); let a = HashMap::, Vec >::from_iter (a.into_iter ()); let b = HashMap::from_iter (b.into_iter ()); assert_eq! (a, b); } #[test] fn store () { let s = Store::new (vec! [ (b"key_dir".to_vec (), StatusQuotas { max_keys: 4, max_key_bytes: 16, max_value_bytes: 16, max_payload_bytes: 128, }), ].into_iter ()); let mut expected_sequence = 0; assert_eq! (s.list_key_dirs (), vec! [ b"key_dir".to_vec (), ]); assert_eq! ( s.set (b"key_dir", b"this key is too long and will cause an error".to_vec (), b"bar".to_vec ()), Err (Error::KeyTooLong) ); assert_eq! ( s.set (b"key_dir", b"foo".to_vec (), b"this value is too long and will cause an error".to_vec ()), Err (Error::ValueTooLong) ); assert_eq! ( s.set (b"invalid_key_dir", b"foo".to_vec (), b"bar".to_vec ()), Err (Error::NoSuchKeyDir) ); let ga = s.get_after (b"key_dir", None).unwrap (); assert_eq! (ga.sequence, expected_sequence); assert_eq! (ga.tuples, vec! []); s.set (b"key_dir", b"foo_1".to_vec (), b"bar_1".to_vec ()).unwrap (); expected_sequence += 1; let ga = s.get_after (b"key_dir", None).unwrap (); assert_eq! (ga.sequence, expected_sequence); assert_eq! (ga.tuples, vec! [ (b"foo_1".to_vec (), b"bar_1".to_vec ()), ]); get_after_eq (&ga, &GetAfter { sequence: expected_sequence, tuples: vec! [ (b"foo_1".to_vec (), b"bar_1".to_vec ()), ] }); s.set (b"key_dir", b"foo_2".to_vec (), b"bar_2".to_vec ()).unwrap (); expected_sequence += 1; let ga = s.get_after (b"key_dir", None).unwrap (); get_after_eq (&ga, &GetAfter { sequence: expected_sequence, tuples: vec! [ (b"foo_1".to_vec (), b"bar_1".to_vec ()), (b"foo_2".to_vec (), b"bar_2".to_vec ()), ] }); s.set (b"key_dir", b"foo_1".to_vec (), b"bar_3".to_vec ()).unwrap (); expected_sequence += 1; let ga = s.get_after (b"key_dir", None).unwrap (); get_after_eq (&ga, &GetAfter { sequence: expected_sequence, tuples: vec! [ (b"foo_1".to_vec (), b"bar_3".to_vec ()), (b"foo_2".to_vec (), b"bar_2".to_vec ()), ] }); let ga = s.get_after (b"key_dir", Some (2)).unwrap (); get_after_eq (&ga, &GetAfter { sequence: expected_sequence, tuples: vec! [ (b"foo_1".to_vec (), b"bar_3".to_vec ()), ] }); let ga = s.get_after (b"key_dir", Some (3)).unwrap (); get_after_eq (&ga, &GetAfter { sequence: expected_sequence, tuples: vec! [] }); } }