#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
#[cfg(all(rocksdb_backend, sled_backend, redb_backend))]
compile_error!(
"the \"rocksdb\", \"redb\" and \"sled\" features may not be enabled at the same time"
);
#[cfg(not(any(rocksdb_backend, sled_backend, redb_backend, wasm)))]
compile_error!("either the \"rocksdb\", \"redb\" or \"sled\" feature must be enabled on native");
use serde::{de::DeserializeOwned, Serialize};
trait StoreImpl {
type GetError;
type SetError;
fn set_string(&mut self, key: &str, value: &str) -> Result<(), Self::SetError> {
self.set(key, &value.to_string())
}
fn get<T: DeserializeOwned>(&self, key: &str) -> Result<T, Self::GetError>;
fn set<T: Serialize>(&mut self, key: &str, value: &T) -> Result<(), Self::SetError>;
fn clear(&mut self) -> Result<(), Self::SetError>;
}
#[cfg(wasm)]
mod local_storage_store;
#[cfg(wasm)]
use local_storage_store::{self as backend};
#[cfg(sled_backend)]
mod sled_store;
#[cfg(sled_backend)]
use sled_store::{self as backend};
#[cfg(rocksdb_backend)]
mod rocksdb_store;
#[cfg(rocksdb_backend)]
use rocksdb_store::{self as backend};
pub use backend::{GetError, SetError};
enum Location<'a> {
PlatformDefault(&'a PlatformDefault),
#[cfg(any(sled_backend, rocksdb_backend, redb_backend))]
CustomPath(&'a std::path::Path),
}
#[cfg(redb_backend)]
mod redb_store;
#[cfg(redb_backend)]
use redb_store::{self as backend};
#[cfg(any(sled_backend, rocksdb_backend, redb_backend))]
mod path;
#[derive(Debug)]
#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Resource))]
pub struct PkvStore {
inner: backend::InnerStore,
}
impl PkvStore {
pub fn new(organization: &str, application: &str) -> Self {
let config = PlatformDefault {
qualifier: None,
organization: organization.to_string(),
application: application.to_string(),
};
Self::new_in_location(&config)
}
pub fn new_with_qualifier(qualifier: &str, organization: &str, application: &str) -> Self {
let config = PlatformDefault {
qualifier: Some(qualifier.to_string()),
organization: organization.to_string(),
application: application.to_string(),
};
Self::new_in_location(&config)
}
#[cfg(any(sled_backend, rocksdb_backend, redb_backend))]
pub fn new_in_dir<P: AsRef<std::path::Path>>(path: P) -> Self {
let inner = backend::InnerStore::new(Location::CustomPath(path.as_ref()));
Self { inner }
}
fn new_in_location(config: &PlatformDefault) -> Self {
let inner = backend::InnerStore::new(Location::PlatformDefault(config));
Self { inner }
}
pub fn set<T: Serialize>(&mut self, key: impl AsRef<str>, value: &T) -> Result<(), SetError> {
self.inner.set(key.as_ref(), value)
}
pub fn set_string(&mut self, key: impl AsRef<str>, value: &str) -> Result<(), SetError> {
self.inner.set_string(key.as_ref(), value)
}
pub fn get<T: DeserializeOwned>(&self, key: impl AsRef<str>) -> Result<T, GetError> {
self.inner.get(key.as_ref())
}
pub fn clear(&mut self) -> Result<(), SetError> {
self.inner.clear()
}
}
struct PlatformDefault {
qualifier: Option<String>,
organization: String,
application: String,
}
#[cfg(test)]
mod tests {
use crate::PkvStore;
use serde::{Deserialize, Serialize};
fn setup() {
#[cfg(target_arch = "wasm32")]
console_error_panic_hook::set_once();
}
#[test]
fn set_string() {
setup();
let mut store = PkvStore::new("BevyPkv", "test_set_string");
store.set_string("hello", "goodbye").unwrap();
let ret = store.get::<String>("hello");
assert_eq!(ret.unwrap(), "goodbye");
}
#[cfg(any(sled_backend, rocksdb_backend, redb_backend))]
#[test]
fn new_in_dir() {
setup();
let dirs = directories::ProjectDirs::from("", "BevyPkv", "test_new_in_dir");
let parent_dir = match dirs.as_ref() {
Some(dirs) => dirs.data_dir(),
None => std::path::Path::new("."), };
let mut store = PkvStore::new_in_dir(parent_dir);
store
.set_string("hello_custom_path", "goodbye_custom_path")
.unwrap();
let ret = store.get::<String>("hello_custom_path");
assert_eq!(ret.unwrap(), "goodbye_custom_path");
}
#[cfg(any(sled_backend, rocksdb_backend, redb_backend))]
#[test]
fn empty_db_not_found() {
use crate::GetError;
setup();
let dir = tempfile::tempdir().expect("failed to create temp dir");
let store = PkvStore::new_in_dir(dir.path());
let err = store.get::<String>("not_there").unwrap_err();
assert!(matches!(err, GetError::NotFound));
}
#[test]
fn clear() {
setup();
let mut store = PkvStore::new("BevyPkv", "test_clear");
store.set_string("key1", "goodbye").unwrap();
store.set_string("key2", "see yeah!").unwrap();
let ret = store.get::<String>("key1").unwrap();
let ret2 = store.get::<String>("key2").unwrap();
assert_eq!(ret, "goodbye");
assert_eq!(ret2, "see yeah!");
store.clear().unwrap();
let ret = store.get::<String>("key1").ok();
let ret2 = store.get::<String>("key2").ok();
assert_eq!((ret, ret2), (None, None))
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
struct User {
name: String,
age: u8,
}
#[test]
fn set() {
setup();
let mut store = PkvStore::new("BevyPkv", "test_set");
let user = User {
name: "alice".to_string(),
age: 32,
};
store.set("user", &user).unwrap();
assert_eq!(store.get::<User>("user").unwrap(), user);
}
}