extern crate time;
use error::CellError;
use redis;
use std::collections::HashMap;
pub trait Store {
fn compare_and_swap_with_ttl(
&mut self,
key: &str,
old: i64,
new: i64,
ttl: time::Duration,
) -> Result<bool, CellError>;
fn get_with_time(&self, key: &str) -> Result<(i64, time::Tm), CellError>;
fn log_debug(&self, message: &str);
fn set_if_not_exists_with_ttl(
&mut self,
key: &str,
value: i64,
ttl: time::Duration,
) -> Result<bool, CellError>;
}
#[derive(Default)]
pub struct MemoryStore {
map: HashMap<String, i64>,
verbose: bool,
}
impl MemoryStore {
pub fn new() -> MemoryStore {
Self::default()
}
pub fn new_verbose() -> MemoryStore {
MemoryStore {
map: HashMap::new(),
verbose: true,
}
}
}
impl Store for MemoryStore {
fn compare_and_swap_with_ttl(
&mut self,
key: &str,
old: i64,
new: i64,
_: time::Duration,
) -> Result<bool, CellError> {
match self.map.get(key) {
Some(n) if *n != old => return Ok(false),
_ => (),
};
self.map.insert(String::from(key), new);
Ok(true)
}
fn get_with_time(&self, key: &str) -> Result<(i64, time::Tm), CellError> {
match self.map.get(key) {
Some(n) => Ok((*n, time::now_utc())),
None => Ok((-1, time::now_utc())),
}
}
fn log_debug(&self, message: &str) {
if self.verbose {
println!("memory_store: {}", message);
}
}
fn set_if_not_exists_with_ttl(
&mut self,
key: &str,
value: i64,
_: time::Duration,
) -> Result<bool, CellError> {
match self.map.get(key) {
Some(_) => Ok(false),
None => {
self.map.insert(String::from(key), value);
Ok(true)
}
}
}
}
pub struct InternalRedisStore<'a> {
r: &'a redis::Redis,
}
impl<'a> InternalRedisStore<'a> {
pub fn new(r: &'a redis::Redis) -> InternalRedisStore<'a> {
InternalRedisStore { r }
}
}
impl<'a> Store for InternalRedisStore<'a> {
fn compare_and_swap_with_ttl(
&mut self,
key: &str,
old: i64,
new: i64,
ttl: time::Duration,
) -> Result<bool, CellError> {
let key = self.r.open_key_writable(key);
match key.read()? {
Some(s) => {
if !s.is_empty() && s.parse::<i64>()? == old {
key.write(new.to_string().as_str())?;
key.set_expire(ttl)?;
Ok(true)
} else {
Ok(false)
}
}
None => Ok(false),
}
}
fn get_with_time(&self, key: &str) -> Result<(i64, time::Tm), CellError> {
let key = self.r.open_key(key);
match key.read()? {
Some(s) => {
let n = s.parse::<i64>()?;
Ok((n, time::now_utc()))
}
None => Ok((-1, time::now_utc())),
}
}
fn log_debug(&self, message: &str) {
self.r.log_debug(message);
}
fn set_if_not_exists_with_ttl(
&mut self,
key: &str,
value: i64,
ttl: time::Duration,
) -> Result<bool, CellError> {
let key = self.r.open_key_writable(key);
let res = if key.is_empty()? {
key.write(value.to_string().as_str())?;
Ok(true)
} else {
Ok(false)
};
key.set_expire(ttl)?;
res
}
}
#[cfg(test)]
mod tests {
extern crate time;
use cell::store::*;
#[test]
fn it_performs_compare_and_swap_with_ttl() {
let mut store = MemoryStore::default();
let res1 =
store.compare_and_swap_with_ttl("foo", 123, 124, time::Duration::zero());
assert_eq!(true, res1.unwrap());
let res2 =
store.compare_and_swap_with_ttl("foo", 124, 125, time::Duration::zero());
assert_eq!(true, res2.unwrap());
let res2 =
store.compare_and_swap_with_ttl("foo", 123, 126, time::Duration::zero());
assert_eq!(false, res2.unwrap());
}
#[test]
fn it_performs_get_with_time() {
let mut store = MemoryStore::default();
let res1 = store.get_with_time("foo");
assert_eq!(-1, res1.unwrap().0);
let _ = store
.set_if_not_exists_with_ttl("foo", 123, time::Duration::zero())
.unwrap();
let res2 = store.get_with_time("foo");
assert_eq!(123, res2.unwrap().0);
}
#[test]
fn it_performs_set_if_not_exists_with_ttl() {
let mut store = MemoryStore::default();
let res1 = store.set_if_not_exists_with_ttl("foo", 123, time::Duration::zero());
assert_eq!(true, res1.unwrap());
let res2 = store.set_if_not_exists_with_ttl("foo", 123, time::Duration::zero());
assert_eq!(false, res2.unwrap());
}
}