unc_sdk/collections/
lazy_option.rs

1//! A persistent lazy option. Stores a value for a given key.
2//! Example:
3//! If the underlying value is large, e.g. the contract needs to store an image, but it doesn't need
4//! to have access to this image at regular calls, then the contract can wrap this image into
5//! `LazyOption` and it will not be deserialized until requested.
6use std::marker::PhantomData;
7
8use borsh::{to_vec, BorshDeserialize, BorshSerialize};
9
10use crate::env;
11use crate::IntoStorageKey;
12use unc_sdk_macros::unc;
13
14const ERR_VALUE_SERIALIZATION: &str = "Cannot serialize value with Borsh";
15const ERR_VALUE_DESERIALIZATION: &str = "Cannot deserialize value with Borsh";
16
17/// An persistent lazy option, that stores a value in the storage.
18#[unc(inside_uncsdk)]
19pub struct LazyOption<T> {
20    storage_key: Vec<u8>,
21    #[borsh(skip)]
22    el: PhantomData<T>,
23}
24
25impl<T> LazyOption<T> {
26    /// Returns `true` if the value is present in the storage.
27    pub fn is_some(&self) -> bool {
28        env::storage_has_key(&self.storage_key)
29    }
30
31    /// Returns `true` if the value is not present in the storage.
32    pub fn is_none(&self) -> bool {
33        !self.is_some()
34    }
35
36    /// Reads the raw value from the storage
37    fn get_raw(&self) -> Option<Vec<u8>> {
38        env::storage_read(&self.storage_key)
39    }
40
41    /// Removes the value from the storage.
42    /// Returns true if the element was present.
43    fn remove_raw(&mut self) -> bool {
44        env::storage_remove(&self.storage_key)
45    }
46
47    /// Removes the raw value from the storage and returns it as an option.
48    fn take_raw(&mut self) -> Option<Vec<u8>> {
49        if self.remove_raw() {
50            Some(env::storage_get_evicted().unwrap())
51        } else {
52            None
53        }
54    }
55
56    fn set_raw(&mut self, raw_value: &[u8]) -> bool {
57        env::storage_write(&self.storage_key, raw_value)
58    }
59
60    fn replace_raw(&mut self, raw_value: &[u8]) -> Option<Vec<u8>> {
61        if self.set_raw(raw_value) {
62            Some(env::storage_get_evicted().unwrap())
63        } else {
64            None
65        }
66    }
67}
68
69impl<T> LazyOption<T>
70where
71    T: BorshSerialize + BorshDeserialize,
72{
73    /// Create a new lazy option with the given `storage_key` and the initial value.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use unc_sdk::collections::LazyOption;
79    ///
80    /// let option: LazyOption<u32> = LazyOption::new(b"l", Some(&42));
81    /// let another_option: LazyOption<u32> = LazyOption::new(b"l", None);
82    /// ```
83    pub fn new<S>(storage_key: S, value: Option<&T>) -> Self
84    where
85        S: IntoStorageKey,
86    {
87        let mut this = Self { storage_key: storage_key.into_storage_key(), el: PhantomData };
88        if let Some(value) = value {
89            this.set(value);
90        }
91        this
92    }
93
94    fn serialize_value(value: &T) -> Vec<u8> {
95        match to_vec(value) {
96            Ok(x) => x,
97            Err(_) => env::panic_str(ERR_VALUE_SERIALIZATION),
98        }
99    }
100
101    fn deserialize_value(raw_value: &[u8]) -> T {
102        match T::try_from_slice(raw_value) {
103            Ok(x) => x,
104            Err(_) => env::panic_str(ERR_VALUE_DESERIALIZATION),
105        }
106    }
107
108    /// Removes the value from storage without reading it.
109    /// Returns whether the value was present.
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// use unc_sdk::collections::LazyOption;
115    ///
116    /// let mut option: LazyOption<u32> = LazyOption::new(b"l", Some(&42));
117    /// assert_eq!(option.remove(), true);
118    /// assert!(option.is_none());
119    /// assert_eq!(option.remove(), false);
120    /// ```
121    pub fn remove(&mut self) -> bool {
122        self.remove_raw()
123    }
124
125    /// Removes the value from storage and returns it as an option.
126    ///
127    /// # Examples
128    ///
129    /// ```
130    /// use unc_sdk::collections::LazyOption;
131    ///
132    /// let mut option: LazyOption<u32> = LazyOption::new(b"l", Some(&42));
133    /// assert_eq!(option.take(), Some(42));
134    /// assert!(option.is_none());
135    /// assert_eq!(option.take(), None);
136    /// ```
137    pub fn take(&mut self) -> Option<T> {
138        self.take_raw().map(|v| Self::deserialize_value(&v))
139    }
140
141    /// Gets the value from storage and returns it as an option.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use unc_sdk::collections::LazyOption;
147    ///
148    /// let mut option: LazyOption<u32> = LazyOption::new(b"l", None);
149    /// assert_eq!(option.get(), None);
150    /// option.set(&42);
151    /// assert_eq!(option.get(), Some(42));
152    /// assert!(option.is_some());
153    /// ```
154    pub fn get(&self) -> Option<T> {
155        self.get_raw().map(|v| Self::deserialize_value(&v))
156    }
157
158    /// Sets the value into the storage without reading the previous value and returns whether the
159    /// previous value was present.
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// use unc_sdk::collections::LazyOption;
165    ///
166    /// let mut option: LazyOption<u32> = LazyOption::new(b"l", None);
167    /// assert_eq!(option.set(&42), false);
168    /// assert_eq!(option.set(&420), true);
169    /// ```
170    pub fn set(&mut self, value: &T) -> bool {
171        self.set_raw(&Self::serialize_value(value))
172    }
173
174    /// Replaces the value in the storage and returns the previous value as an option.
175    ///
176    /// # Examples
177    ///
178    /// ```
179    /// use unc_sdk::collections::LazyOption;
180    ///
181    /// let mut option: LazyOption<u32> = LazyOption::new(b"l", None);
182    /// assert_eq!(option.replace(&42), None);
183    /// assert_eq!(option.replace(&420), Some(42));
184    /// ```
185    pub fn replace(&mut self, value: &T) -> Option<T> {
186        self.replace_raw(&Self::serialize_value(value)).map(|v| Self::deserialize_value(&v))
187    }
188}
189
190impl<T> std::fmt::Debug for LazyOption<T>
191where
192    T: std::fmt::Debug + BorshSerialize + BorshDeserialize,
193{
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195        if cfg!(feature = "expensive-debug") {
196            self.get().fmt(f)
197        } else {
198            f.debug_struct("LazyOption").field("storage_key", &self.storage_key).finish()
199        }
200    }
201}
202
203#[cfg(not(target_arch = "wasm32"))]
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    pub fn test_all() {
210        let mut a = LazyOption::new(b"a", None);
211        assert!(a.is_none());
212        a.set(&42u32);
213        assert!(a.is_some());
214        assert_eq!(a.get(), Some(42));
215        assert!(a.is_some());
216        assert_eq!(a.replace(&95), Some(42));
217        assert!(a.is_some());
218        assert_eq!(a.take(), Some(95));
219        assert!(a.is_none());
220        assert_eq!(a.replace(&105), None);
221        assert!(a.is_some());
222        assert_eq!(a.get(), Some(105));
223        assert!(a.remove());
224        assert!(a.is_none());
225        assert_eq!(a.get(), None);
226        assert_eq!(a.take(), None);
227        assert!(a.is_none());
228    }
229
230    #[test]
231    pub fn test_multi() {
232        let mut a = LazyOption::new(b"a", None);
233        let mut b = LazyOption::new(b"b", None);
234        assert!(a.is_none());
235        assert!(b.is_none());
236        a.set(&42u32);
237        assert!(b.is_none());
238        assert!(a.is_some());
239        assert_eq!(a.get(), Some(42));
240        b.set(&32u32);
241        assert!(a.is_some());
242        assert!(b.is_some());
243        assert_eq!(a.get(), Some(42));
244        assert_eq!(b.get(), Some(32));
245    }
246
247    #[test]
248    pub fn test_init_value() {
249        let a = LazyOption::new(b"a", Some(&42u32));
250        assert!(a.is_some());
251        assert_eq!(a.get(), Some(42));
252    }
253
254    #[test]
255    pub fn test_debug() {
256        let mut lazy_option = LazyOption::new(b"m", None);
257        if cfg!(feature = "expensive-debug") {
258            assert_eq!(format!("{:?}", lazy_option), "None");
259        } else {
260            assert_eq!(format!("{:?}", lazy_option), "LazyOption { storage_key: [109] }");
261        }
262
263        lazy_option.set(&1u64);
264        if cfg!(feature = "expensive-debug") {
265            assert_eq!(format!("{:?}", lazy_option), "Some(1)");
266        } else {
267            assert_eq!(format!("{:?}", lazy_option), "LazyOption { storage_key: [109] }");
268        }
269    }
270}