use crate::time::system_time_conversion::{
checked_system_time_to_micros_from_epoch, micros_from_epoch_to_system_time,
};
use futures::future::{BoxFuture, FutureExt, TryFutureExt};
use log::error;
use std::time::SystemTime;
mod memory;
pub use memory::MemStorage;
#[cfg(test)]
mod stub;
#[cfg(test)]
pub use stub::StubStorage;
pub trait Storage {
type Error: std::error::Error;
fn get_string<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<String>>;
fn get_int<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<i64>>;
fn get_bool<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<bool>>;
fn set_string<'a>(
&'a mut self,
key: &'a str,
value: &'a str,
) -> BoxFuture<'a, Result<(), Self::Error>>;
fn set_int<'a>(
&'a mut self,
key: &'a str,
value: i64,
) -> BoxFuture<'a, Result<(), Self::Error>>;
fn set_bool<'a>(
&'a mut self,
key: &'a str,
value: bool,
) -> BoxFuture<'a, Result<(), Self::Error>>;
fn remove<'a>(&'a mut self, key: &'a str) -> BoxFuture<'a, Result<(), Self::Error>>;
fn commit(&mut self) -> BoxFuture<'_, Result<(), Self::Error>>;
}
pub trait StorageExt: Storage {
fn set_option_int<'a>(
&'a mut self,
key: &'a str,
value: Option<i64>,
) -> BoxFuture<'a, Result<(), Self::Error>> {
match value {
Some(value) => self.set_int(key, value),
None => self.remove(key),
}
}
fn get_time<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<SystemTime>> {
self.get_int(key)
.map(|option| option.map(micros_from_epoch_to_system_time))
.boxed()
}
fn set_time<'a>(
&'a mut self,
key: &'a str,
value: impl Into<SystemTime>,
) -> BoxFuture<'a, Result<(), Self::Error>> {
self.set_option_int(key, checked_system_time_to_micros_from_epoch(value.into()))
}
fn remove_or_log<'a>(&'a mut self, key: &'a str) -> BoxFuture<'a, ()> {
self.remove(key)
.unwrap_or_else(move |e| error!("Unable to remove {}: {}", key, e))
.boxed()
}
fn commit_or_log(&mut self) -> BoxFuture<'_, ()> {
self.commit()
.unwrap_or_else(|e| error!("Unable to commit persisted data: {}", e))
.boxed()
}
}
impl<T> StorageExt for T where T: Storage {}
pub mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::time::Duration;
pub async fn do_test_set_get_remove_string<S: Storage>(storage: &mut S) {
assert_eq!(None, storage.get_string("some key").await);
storage.set_string("some key", "some value").await.unwrap();
storage.commit().await.unwrap();
assert_eq!(
Some("some value".to_string()),
storage.get_string("some key").await
);
storage
.set_string("some key", "some other value")
.await
.unwrap();
storage.commit().await.unwrap();
assert_eq!(
Some("some other value".to_string()),
storage.get_string("some key").await
);
storage.remove("some key").await.unwrap();
storage.commit().await.unwrap();
assert_eq!(None, storage.get_string("some key").await);
}
pub async fn do_test_set_get_remove_int<S: Storage>(storage: &mut S) {
assert_eq!(None, storage.get_int("some int key").await);
storage.set_int("some int key", 42).await.unwrap();
storage.commit().await.unwrap();
assert_eq!(Some(42), storage.get_int("some int key").await);
storage.set_int("some int key", 1).await.unwrap();
storage.commit().await.unwrap();
assert_eq!(Some(1), storage.get_int("some int key").await);
storage.remove("some int key").await.unwrap();
storage.commit().await.unwrap();
assert_eq!(None, storage.get_int("some int key").await);
}
pub async fn do_test_set_option_int<S: Storage>(storage: &mut S) {
assert_eq!(None, storage.get_int("some int key").await);
storage
.set_option_int("some int key", Some(42))
.await
.unwrap();
storage.commit().await.unwrap();
assert_eq!(Some(42), storage.get_int("some int key").await);
storage.set_option_int("some int key", None).await.unwrap();
storage.commit().await.unwrap();
assert_eq!(None, storage.get_int("some int key").await);
}
pub async fn do_test_set_get_remove_bool<S: Storage>(storage: &mut S) {
assert_eq!(None, storage.get_bool("some bool key").await);
storage.set_bool("some bool key", false).await.unwrap();
storage.commit().await.unwrap();
assert_eq!(Some(false), storage.get_bool("some bool key").await);
storage.set_bool("some bool key", true).await.unwrap();
storage.commit().await.unwrap();
assert_eq!(Some(true), storage.get_bool("some bool key").await);
storage.remove("some bool key").await.unwrap();
storage.commit().await.unwrap();
assert_eq!(None, storage.get_bool("some bool key").await);
}
pub async fn do_test_set_get_remove_time<S: Storage>(storage: &mut S) {
assert_eq!(None, storage.get_time("some time key").await);
storage
.set_time("some time key", SystemTime::UNIX_EPOCH)
.await
.unwrap();
storage.commit().await.unwrap();
assert_eq!(
Some(SystemTime::UNIX_EPOCH),
storage.get_time("some time key").await
);
let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1234);
storage.set_time("some time key", time).await.unwrap();
storage.commit().await.unwrap();
assert_eq!(Some(time), storage.get_time("some time key").await);
storage.remove("some time key").await.unwrap();
storage.commit().await.unwrap();
assert_eq!(None, storage.get_time("some time key").await);
}
pub async fn do_return_none_for_wrong_value_type<S: Storage>(storage: &mut S) {
storage.set_int("some int key", 42).await.unwrap();
assert_eq!(None, storage.get_string("some int key").await);
}
pub async fn do_ensure_no_error_remove_nonexistent_key<S: Storage>(storage: &mut S) {
storage.set_string("some key", "some value").await.unwrap();
storage.commit().await.unwrap();
storage.remove("some key").await.unwrap();
storage.remove("some key").await.unwrap();
}
}