use std::sync::OnceLock;
pub trait PersistBackend: Send + Sync {
fn get(&self, key: &str) -> Result<Option<Vec<u8>>, String>;
fn set(&self, key: &str, value: &[u8]) -> Result<(), String>;
fn delete(&self, key: &str) -> Result<(), String>;
}
static BACKEND: OnceLock<Box<dyn PersistBackend>> = OnceLock::new();
pub fn set_persist_backend(backend: Box<dyn PersistBackend>) -> bool {
BACKEND.set(backend).is_ok()
}
pub fn persist_backend() -> Option<&'static dyn PersistBackend> {
BACKEND.get().map(|b| b.as_ref())
}
pub trait PersistValue: Sized {
fn to_persist_bytes(&self) -> Vec<u8>;
fn from_persist_bytes(bytes: &[u8]) -> Option<Self>;
}
impl PersistValue for String {
fn to_persist_bytes(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
fn from_persist_bytes(bytes: &[u8]) -> Option<Self> {
String::from_utf8(bytes.to_vec()).ok()
}
}
impl PersistValue for Vec<u8> {
fn to_persist_bytes(&self) -> Vec<u8> {
self.clone()
}
fn from_persist_bytes(bytes: &[u8]) -> Option<Self> {
Some(bytes.to_vec())
}
}
impl PersistValue for bool {
fn to_persist_bytes(&self) -> Vec<u8> {
vec![u8::from(*self)]
}
fn from_persist_bytes(bytes: &[u8]) -> Option<Self> {
match bytes {
[0] => Some(false),
[1] => Some(true),
_ => None,
}
}
}
macro_rules! persist_le_number {
($($t:ty),*) => {$(
impl PersistValue for $t {
fn to_persist_bytes(&self) -> Vec<u8> {
self.to_le_bytes().to_vec()
}
fn from_persist_bytes(bytes: &[u8]) -> Option<Self> {
Some(<$t>::from_le_bytes(bytes.try_into().ok()?))
}
}
)*};
}
persist_le_number!(i32, i64, u32, u64, f32, f64);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn primitives_round_trip() {
assert_eq!(String::from_persist_bytes(&"héllo".to_string().to_persist_bytes()), Some("héllo".to_string()));
assert_eq!(bool::from_persist_bytes(&true.to_persist_bytes()), Some(true));
assert_eq!(i64::from_persist_bytes(&(-42i64).to_persist_bytes()), Some(-42));
assert_eq!(f64::from_persist_bytes(&(1.5f64).to_persist_bytes()), Some(1.5));
assert_eq!(u32::from_persist_bytes(&7u32.to_persist_bytes()), Some(7));
}
#[test]
fn stale_bytes_decode_to_none_not_panic() {
assert_eq!(i32::from_persist_bytes(b"way-too-long-for-i32"), None);
assert_eq!(bool::from_persist_bytes(&[9]), None);
assert_eq!(String::from_persist_bytes(&[0xFF, 0xFE]), None);
}
}