mod impls;
use borsh::{BorshDeserialize, BorshSerialize};
use once_cell::unsync::OnceCell;
use unc_sdk_macros::unc;
use crate::env;
use crate::store::lazy::{load_and_deserialize, serialize_and_store};
use crate::utils::{CacheEntry, EntryState};
use crate::IntoStorageKey;
#[unc(inside_uncsdk)]
pub struct LazyOption<T>
where
T: BorshSerialize,
{
prefix: Box<[u8]>,
#[borsh(skip, bound(deserialize = ""))] cache: OnceCell<CacheEntry<T>>,
}
impl<T> LazyOption<T>
where
T: BorshSerialize,
{
pub fn new<S>(prefix: S, value: Option<T>) -> Self
where
S: IntoStorageKey,
{
let cache = match value {
Some(value) => CacheEntry::new_modified(Some(value)),
None => CacheEntry::new_cached(None),
};
Self { prefix: prefix.into_storage_key().into_boxed_slice(), cache: OnceCell::from(cache) }
}
pub fn set(&mut self, value: Option<T>) {
if let Some(v) = self.cache.get_mut() {
*v.value_mut() = value;
} else {
self.cache
.set(CacheEntry::new_modified(value))
.unwrap_or_else(|_| env::abort());
}
}
pub fn flush(&mut self) {
if let Some(v) = self.cache.get_mut() {
if !v.is_modified() {
return;
}
match v.value().as_ref() {
Some(value) => serialize_and_store(&self.prefix, value),
None => {
env::storage_remove(&self.prefix);
}
}
v.replace_state(EntryState::Cached);
}
}
}
impl<T> LazyOption<T>
where
T: BorshSerialize + BorshDeserialize,
{
pub fn get(&self) -> &Option<T> {
let entry = self.cache.get_or_init(|| load_and_deserialize(&self.prefix));
entry.value()
}
pub fn get_mut(&mut self) -> &mut Option<T> {
self.cache.get_or_init(|| load_and_deserialize(&self.prefix));
let entry = self.cache.get_mut().unwrap_or_else(|| env::abort());
entry.value_mut()
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_lazy_option() {
let mut a = LazyOption::new(b"a", None);
assert!(a.is_none());
assert!(!env::storage_has_key(b"a"));
a.set(Some(42u32));
assert!(a.is_some());
assert_eq!(a.get(), &Some(42));
a.flush();
assert!(env::storage_has_key(b"a"));
assert_eq!(u32::try_from_slice(&env::storage_read(b"a").unwrap()).unwrap(), 42);
*a = Some(49u32);
assert!(a.is_some());
assert_eq!(a.get(), &Some(49));
let old = a.replace(69u32);
assert!(a.is_some());
assert_eq!(old, Some(49));
let taken = a.take();
assert!(a.is_none());
assert_eq!(taken, Some(69));
drop(a);
assert!(!env::storage_has_key(b"a"));
}
#[test]
pub fn test_debug() {
let mut lazy_option = LazyOption::new(b"m", None);
if cfg!(feature = "expensive-debug") {
assert_eq!(format!("{:?}", lazy_option), "None");
} else {
assert_eq!(
format!("{:?}", lazy_option),
"LazyOption { storage_key: [109], cache: Some(CacheEntry { value: None, state: Cached }) }"
);
}
*lazy_option = Some(1u8);
if cfg!(feature = "expensive-debug") {
assert_eq!(format!("{:?}", lazy_option), "Some(1)");
} else {
assert_eq!(
format!("{:?}", lazy_option),
"LazyOption { storage_key: [109], cache: Some(CacheEntry { value: Some(1), state: Modified }) }"
);
}
let serialized = borsh::to_vec(&lazy_option).unwrap();
drop(lazy_option);
let lazy_option = LazyOption::<u8>::try_from_slice(&serialized).unwrap();
if cfg!(feature = "expensive-debug") {
assert_eq!(format!("{:?}", lazy_option), "Some(1)");
} else {
assert_eq!(
format!("{:?}", lazy_option),
"LazyOption { storage_key: [109], cache: None }"
);
}
}
}