icarus_canister/
easy_storage.rs

1//! Simplified storage patterns for common use cases
2
3use crate::memory::get_memory;
4use crate::result::{IcarusError, IcarusResult};
5use ic_stable_structures::memory_manager::VirtualMemory;
6use ic_stable_structures::DefaultMemoryImpl;
7use ic_stable_structures::{StableBTreeMap, StableCell, Storable};
8use std::cell::RefCell;
9
10type Memory = VirtualMemory<DefaultMemoryImpl>;
11
12/// A simplified storage pattern that reduces boilerplate
13///
14/// # Example
15/// ```ignore
16/// // This macro requires IC canister context and stable structures
17/// use icarus_canister::prelude::*;
18///
19/// // Define your storage
20/// icarus_storage! {
21///     USERS: Map<String, User> = 0;
22///     COUNTER: Cell<u64> = 1;
23/// }
24///
25/// // Use it directly
26/// fn add_user(id: String, user: User) -> IcarusResult<()> {
27///     USERS.insert(id, user)?;
28///     Ok(())
29/// }
30/// ```
31#[macro_export]
32macro_rules! icarus_storage {
33    (
34        $($name:ident: Map<$key:ty, $value:ty> = $id:expr;)*
35        $($counter:ident: Cell<$ctype:ty> = $cid:expr;)*
36    ) => {
37        thread_local! {
38            $(
39                static $name: $crate::easy_storage::StorageMap<$key, $value> =
40                    $crate::easy_storage::StorageMap::new($id);
41            )*
42            $(
43                static $counter: $crate::easy_storage::CounterCell =
44                    $crate::easy_storage::CounterCell::new($cid);
45            )*
46        }
47
48        // Generate convenience accessor structs
49        $(
50            #[allow(non_camel_case_types)]
51            pub struct $name;
52
53            impl $name {
54                pub fn insert(key: $key, value: $value) -> $crate::result::IcarusResult<Option<$value>> {
55                    $name.with(|s| s.insert(key, value))
56                }
57
58                pub fn get(key: &$key) -> Option<$value> {
59                    $name.with(|s| s.get(key))
60                }
61
62                pub fn remove(key: &$key) -> Option<$value> {
63                    $name.with(|s| s.remove(key))
64                }
65
66                pub fn contains(key: &$key) -> bool {
67                    $name.with(|s| s.contains(key))
68                }
69
70                pub fn len() -> u64 {
71                    $name.with(|s| s.len())
72                }
73
74                pub fn is_empty() -> bool {
75                    $name.with(|s| s.is_empty())
76                }
77
78                pub fn clear() {
79                    $name.with(|s| s.clear())
80                }
81
82                pub fn iter<F>(f: F)
83                where
84                    F: FnMut(&$key, &$value)
85                {
86                    $name.with(|s| s.iter(f))
87                }
88
89                pub fn values() -> Vec<$value> {
90                    $name.with(|s| s.values())
91                }
92            }
93        )*
94
95        $(
96            #[allow(non_camel_case_types)]
97            pub struct $counter;
98
99            impl $counter {
100                pub fn get() -> $ctype {
101                    $counter.with(|c| c.get())
102                }
103
104                pub fn set(value: $ctype) -> $crate::result::IcarusResult<()> {
105                    $counter.with(|c| c.set(value))
106                }
107
108                pub fn increment() -> $ctype {
109                    $counter.with(|c| c.increment())
110                }
111
112                pub fn decrement() -> $ctype {
113                    $counter.with(|c| c.decrement())
114                }
115            }
116        )*
117    };
118}
119
120/// A thread-safe storage map with automatic memory management
121pub struct StorageMap<K, V>
122where
123    K: Storable + Ord + Clone,
124    V: Storable + Clone,
125{
126    inner: RefCell<StableBTreeMap<K, V, Memory>>,
127}
128
129impl<K, V> StorageMap<K, V>
130where
131    K: Storable + Ord + Clone,
132    V: Storable + Clone,
133{
134    pub fn new(memory_id: u8) -> Self {
135        Self {
136            inner: RefCell::new(StableBTreeMap::init(get_memory(memory_id))),
137        }
138    }
139
140    pub fn insert(&self, key: K, value: V) -> IcarusResult<Option<V>> {
141        Ok(self.inner.borrow_mut().insert(key, value))
142    }
143
144    pub fn get(&self, key: &K) -> Option<V> {
145        self.inner.borrow().get(key)
146    }
147
148    pub fn remove(&self, key: &K) -> Option<V> {
149        self.inner.borrow_mut().remove(key)
150    }
151
152    pub fn contains(&self, key: &K) -> bool {
153        self.inner.borrow().contains_key(key)
154    }
155
156    pub fn len(&self) -> u64 {
157        self.inner.borrow().len()
158    }
159
160    pub fn is_empty(&self) -> bool {
161        self.inner.borrow().is_empty()
162    }
163
164    pub fn clear(&self) {
165        self.inner.borrow_mut().clear_new()
166    }
167
168    pub fn iter<F>(&self, mut f: F)
169    where
170        F: FnMut(&K, &V),
171    {
172        for (k, v) in self.inner.borrow().iter() {
173            f(&k, &v);
174        }
175    }
176
177    pub fn values(&self) -> Vec<V> {
178        self.inner.borrow().iter().map(|(_, v)| v).collect()
179    }
180
181    pub fn get_or_insert<F>(&self, key: K, f: F) -> V
182    where
183        F: FnOnce() -> V,
184    {
185        if let Some(v) = self.get(&key) {
186            v
187        } else {
188            let value = f();
189            self.inner.borrow_mut().insert(key, value.clone());
190            value
191        }
192    }
193}
194
195/// A thread-safe storage cell for single values
196pub struct StorageCell<T>
197where
198    T: Storable + Default + Clone,
199{
200    inner: RefCell<StableCell<T, Memory>>,
201}
202
203impl<T> StorageCell<T>
204where
205    T: Storable + Default + Clone,
206{
207    pub fn new(memory_id: u8) -> Self {
208        Self {
209            inner: RefCell::new(
210                StableCell::init(get_memory(memory_id), T::default())
211                    .expect("Failed to init storage cell"),
212            ),
213        }
214    }
215
216    pub fn get(&self) -> T {
217        self.inner.borrow().get().clone()
218    }
219
220    pub fn set(&self, value: T) -> IcarusResult<()> {
221        self.inner
222            .borrow_mut()
223            .set(value)
224            .map(|_| ())
225            .map_err(|_| IcarusError::storage("Failed to set value"))
226    }
227}
228
229// Specialized wrapper for u64 counters with increment/decrement
230pub struct CounterCell {
231    inner: RefCell<StableCell<u64, Memory>>,
232}
233
234impl CounterCell {
235    pub fn new(memory_id: u8) -> Self {
236        Self {
237            inner: RefCell::new(
238                StableCell::init(get_memory(memory_id), 0u64).expect("Failed to init counter cell"),
239            ),
240        }
241    }
242
243    pub fn get(&self) -> u64 {
244        *self.inner.borrow().get()
245    }
246
247    pub fn set(&self, value: u64) -> IcarusResult<()> {
248        self.inner
249            .borrow_mut()
250            .set(value)
251            .map(|_| ())
252            .map_err(|_| IcarusError::storage("Failed to set counter value"))
253    }
254
255    pub fn increment(&self) -> u64 {
256        let current = self.get();
257        let next = current + 1;
258        let _ = self.set(next);
259        next
260    }
261
262    pub fn decrement(&self) -> u64 {
263        let current = self.get();
264        let next = current.saturating_sub(1);
265        let _ = self.set(next);
266        next
267    }
268}