use std::marker::PhantomData;
use borsh::{BorshDeserialize, BorshSerialize, to_vec};
use crate::IntoStorageKey;
use crate::env;
use near_sdk_macros::near;
const ERR_VALUE_SERIALIZATION: &str = "Cannot serialize value with Borsh";
const ERR_VALUE_DESERIALIZATION: &str = "Cannot deserialize value with Borsh";
#[near(inside_nearsdk)]
pub struct LazyOption<T> {
storage_key: Vec<u8>,
#[borsh(skip)]
el: PhantomData<T>,
}
impl<T> LazyOption<T> {
pub fn is_some(&self) -> bool {
env::storage_has_key(&self.storage_key)
}
pub fn is_none(&self) -> bool {
!self.is_some()
}
fn get_raw(&self) -> Option<Vec<u8>> {
env::storage_read(&self.storage_key)
}
fn remove_raw(&mut self) -> bool {
env::storage_remove(&self.storage_key)
}
fn take_raw(&mut self) -> Option<Vec<u8>> {
if self.remove_raw() { Some(env::storage_get_evicted().unwrap()) } else { None }
}
fn set_raw(&mut self, raw_value: &[u8]) -> bool {
env::storage_write(&self.storage_key, raw_value)
}
fn replace_raw(&mut self, raw_value: &[u8]) -> Option<Vec<u8>> {
if self.set_raw(raw_value) { Some(env::storage_get_evicted().unwrap()) } else { None }
}
}
impl<T> LazyOption<T>
where
T: BorshSerialize + BorshDeserialize,
{
pub fn new<S>(storage_key: S, value: Option<&T>) -> Self
where
S: IntoStorageKey,
{
let mut this = Self { storage_key: storage_key.into_storage_key(), el: PhantomData };
if let Some(value) = value {
this.set(value);
}
this
}
fn serialize_value(value: &T) -> Vec<u8> {
match to_vec(value) {
Ok(x) => x,
Err(_) => env::panic_str(ERR_VALUE_SERIALIZATION),
}
}
fn deserialize_value(raw_value: &[u8]) -> T {
match T::try_from_slice(raw_value) {
Ok(x) => x,
Err(_) => env::panic_str(ERR_VALUE_DESERIALIZATION),
}
}
pub fn remove(&mut self) -> bool {
self.remove_raw()
}
pub fn take(&mut self) -> Option<T> {
self.take_raw().map(|v| Self::deserialize_value(&v))
}
pub fn get(&self) -> Option<T> {
self.get_raw().map(|v| Self::deserialize_value(&v))
}
pub fn set(&mut self, value: &T) -> bool {
self.set_raw(&Self::serialize_value(value))
}
pub fn replace(&mut self, value: &T) -> Option<T> {
self.replace_raw(&Self::serialize_value(value)).map(|v| Self::deserialize_value(&v))
}
}
impl<T> std::fmt::Debug for LazyOption<T>
where
T: std::fmt::Debug + BorshSerialize + BorshDeserialize,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if cfg!(feature = "expensive-debug") {
self.get().fmt(f)
} else {
f.debug_struct("LazyOption").field("storage_key", &self.storage_key).finish()
}
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_all() {
let mut a = LazyOption::new(b"a", None);
assert!(a.is_none());
a.set(&42u32);
assert!(a.is_some());
assert_eq!(a.get(), Some(42));
assert!(a.is_some());
assert_eq!(a.replace(&95), Some(42));
assert!(a.is_some());
assert_eq!(a.take(), Some(95));
assert!(a.is_none());
assert_eq!(a.replace(&105), None);
assert!(a.is_some());
assert_eq!(a.get(), Some(105));
assert!(a.remove());
assert!(a.is_none());
assert_eq!(a.get(), None);
assert_eq!(a.take(), None);
assert!(a.is_none());
}
#[test]
pub fn test_multi() {
let mut a = LazyOption::new(b"a", None);
let mut b = LazyOption::new(b"b", None);
assert!(a.is_none());
assert!(b.is_none());
a.set(&42u32);
assert!(b.is_none());
assert!(a.is_some());
assert_eq!(a.get(), Some(42));
b.set(&32u32);
assert!(a.is_some());
assert!(b.is_some());
assert_eq!(a.get(), Some(42));
assert_eq!(b.get(), Some(32));
}
#[test]
pub fn test_init_value() {
let a = LazyOption::new(b"a", Some(&42u32));
assert!(a.is_some());
assert_eq!(a.get(), Some(42));
}
#[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] }");
}
lazy_option.set(&1u64);
if cfg!(feature = "expensive-debug") {
assert_eq!(format!("{:?}", lazy_option), "Some(1)");
} else {
assert_eq!(format!("{:?}", lazy_option), "LazyOption { storage_key: [109] }");
}
}
}