use serde::{de::DeserializeOwned, ser::Serialize};
use std::marker::PhantomData;
use cosmwasm::errors::Result;
use cosmwasm::traits::{ReadonlyStorage, Storage};
use crate::namespace_helpers::{get_with_prefix, key_prefix, key_prefix_nested, set_with_prefix};
use crate::type_helpers::{may_deserialize, must_deserialize, serialize};
pub fn bucket<'a, S: Storage, T>(namespace: &[u8], storage: &'a mut S) -> Bucket<'a, S, T>
where
T: Serialize + DeserializeOwned,
{
Bucket::new(namespace, storage)
}
pub fn bucket_read<'a, S: ReadonlyStorage, T>(
namespace: &[u8],
storage: &'a S,
) -> ReadonlyBucket<'a, S, T>
where
T: Serialize + DeserializeOwned,
{
ReadonlyBucket::new(namespace, storage)
}
pub struct Bucket<'a, S: Storage, T>
where
T: Serialize + DeserializeOwned,
{
storage: &'a mut S,
data: PhantomData<&'a T>,
prefix: Vec<u8>,
}
impl<'a, S: Storage, T> Bucket<'a, S, T>
where
T: Serialize + DeserializeOwned,
{
pub fn new(namespace: &[u8], storage: &'a mut S) -> Self {
Bucket {
prefix: key_prefix(namespace),
storage,
data: PhantomData,
}
}
pub fn multilevel(namespaces: &[&[u8]], storage: &'a mut S) -> Self {
Bucket {
prefix: key_prefix_nested(namespaces),
storage,
data: PhantomData,
}
}
pub fn save(&mut self, key: &[u8], data: &T) -> Result<()> {
set_with_prefix(self.storage, &self.prefix, key, &serialize(data)?);
Ok(())
}
pub fn load(&self, key: &[u8]) -> Result<T> {
let value = get_with_prefix(self.storage, &self.prefix, key);
must_deserialize(&value)
}
pub fn may_load(&self, key: &[u8]) -> Result<Option<T>> {
let value = get_with_prefix(self.storage, &self.prefix, key);
may_deserialize(&value)
}
pub fn update(&mut self, key: &[u8], action: &dyn Fn(Option<T>) -> Result<T>) -> Result<T> {
let input = self.may_load(key)?;
let output = action(input)?;
self.save(key, &output)?;
Ok(output)
}
}
pub struct ReadonlyBucket<'a, S: ReadonlyStorage, T>
where
T: Serialize + DeserializeOwned,
{
storage: &'a S,
data: PhantomData<&'a T>,
prefix: Vec<u8>,
}
impl<'a, S: ReadonlyStorage, T> ReadonlyBucket<'a, S, T>
where
T: Serialize + DeserializeOwned,
{
pub fn new(namespace: &[u8], storage: &'a S) -> Self {
ReadonlyBucket {
prefix: key_prefix(namespace),
storage,
data: PhantomData,
}
}
pub fn multilevel(namespaces: &[&[u8]], storage: &'a S) -> Self {
ReadonlyBucket {
prefix: key_prefix_nested(namespaces),
storage,
data: PhantomData,
}
}
pub fn load(&self, key: &[u8]) -> Result<T> {
let value = get_with_prefix(self.storage, &self.prefix, key);
must_deserialize(&value)
}
pub fn may_load(&self, key: &[u8]) -> Result<Option<T>> {
let value = get_with_prefix(self.storage, &self.prefix, key);
may_deserialize(&value)
}
}
#[cfg(test)]
mod test {
use super::*;
use cosmwasm::errors::{contract_err, NotFound};
use cosmwasm::mock::MockStorage;
use serde::{Deserialize, Serialize};
use snafu::OptionExt;
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
struct Data {
pub name: String,
pub age: i32,
}
#[test]
fn store_and_load() {
let mut store = MockStorage::new();
let mut bucket = bucket::<_, Data>(b"data", &mut store);
let data = Data {
name: "Maria".to_string(),
age: 42,
};
bucket.save(b"maria", &data).unwrap();
let loaded = bucket.load(b"maria").unwrap();
assert_eq!(data, loaded);
}
#[test]
fn readonly_works() {
let mut store = MockStorage::new();
let mut bucket = bucket::<_, Data>(b"data", &mut store);
let data = Data {
name: "Maria".to_string(),
age: 42,
};
bucket.save(b"maria", &data).unwrap();
let reader = bucket_read::<_, Data>(b"data", &mut store);
assert!(reader.load(b"john").is_err());
assert_eq!(reader.may_load(b"john").unwrap(), None);
let loaded = reader.load(b"maria").unwrap();
assert_eq!(data, loaded);
}
#[test]
fn buckets_isolated() {
let mut store = MockStorage::new();
let mut bucket1 = bucket::<_, Data>(b"data", &mut store);
let data = Data {
name: "Maria".to_string(),
age: 42,
};
bucket1.save(b"maria", &data).unwrap();
let mut bucket2 = bucket::<_, Data>(b"dat", &mut store);
let data2 = Data {
name: "Amen".to_string(),
age: 67,
};
bucket2.save(b"amaria", &data2).unwrap();
let reader = bucket_read::<_, Data>(b"data", &store);
let loaded = reader.load(b"maria").unwrap();
assert_eq!(data, loaded);
assert_eq!(None, reader.may_load(b"amaria").unwrap());
let reader2 = bucket_read::<_, Data>(b"dat", &store);
let loaded2 = reader2.load(b"amaria").unwrap();
assert_eq!(data2, loaded2);
assert_eq!(None, reader2.may_load(b"maria").unwrap());
}
#[test]
fn update_success() {
let mut store = MockStorage::new();
let mut bucket = bucket::<_, Data>(b"data", &mut store);
let init = Data {
name: "Maria".to_string(),
age: 42,
};
bucket.save(b"maria", &init).unwrap();
let birthday = |mayd: Option<Data>| -> Result<Data> {
let mut d = mayd.context(NotFound { kind: "Data" })?;
d.age += 1;
Ok(d)
};
let output = bucket.update(b"maria", &birthday).unwrap();
let expected = Data {
name: "Maria".to_string(),
age: 43,
};
assert_eq!(output, expected);
let loaded = bucket.load(b"maria").unwrap();
assert_eq!(loaded, expected);
}
#[test]
fn update_fails_on_error() {
let mut store = MockStorage::new();
let mut bucket = bucket::<_, Data>(b"data", &mut store);
let init = Data {
name: "Maria".to_string(),
age: 42,
};
bucket.save(b"maria", &init).unwrap();
let output = bucket.update(b"maria", &|_d| contract_err("cuz i feel like it"));
assert!(output.is_err());
let loaded = bucket.load(b"maria").unwrap();
assert_eq!(loaded, init);
}
#[test]
fn update_handles_on_no_data() {
let mut store = MockStorage::new();
let mut bucket = bucket::<_, Data>(b"data", &mut store);
let init_value = Data {
name: "Maria".to_string(),
age: 42,
};
let output = bucket
.update(b"maria", &|d| match d {
Some(_) => contract_err("Ensure this was empty"),
None => Ok(init_value.clone()),
})
.unwrap();
assert_eq!(output, init_value);
let loaded = bucket.load(b"maria").unwrap();
assert_eq!(loaded, init_value);
}
}