-
Notifications
You must be signed in to change notification settings - Fork 11
/
lib.rs
141 lines (131 loc) · 5.79 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! This crate provides a safe way to use canister state, as well as versioned storage.
//!
//! * For in memory storage use [`IcStorage`]. Structs that derive [`IcStorage`] must also implement `std::fmt::Default`.
//! * For versioned storage see [`crate::stable`].
//!
//! ```
//! # ic_exports::ic_kit::MockContext::new().inject();
//! use ic_storage::IcStorage;
//!
//! #[derive(IcStorage, Default)]
//! struct MyCanisterState {
//! value: u32,
//! }
//!
//! let local_state = MyCanisterState::get();
//! assert_eq!(local_state.borrow().value, 0);
//! local_state.borrow_mut().value = 42;
//! assert_eq!(local_state.borrow().value, 42);
//! ```
//!
//! Unfortunately, you cannot use the derive macro to create storage for generic types, as there is
//! no way to know in advance, which concrete types are going to be stored. Instead, you can use
//! `generic_derive!` macro for them:
//!
//! ```
//! # ic_exports::ic_kit::MockContext::new().inject();
//! use ic_storage::IcStorage;
//!
//! #[derive(Default)]
//! struct GenericStorage<A, B> {
//! val1: A,
//! val2: B,
//! }
//!
//! ic_storage::generic_derive!(GenericStorage<u32, String>);
//!
//! let local_state = GenericStorage::<u32, String>::get();
//! assert_eq!(local_state.borrow().val1, 0);
//! assert_eq!(local_state.borrow().val2, "".to_string());
//! ```
//!
//! `IcStorage` derive macro uses `RefCell` to control the access to the state struct, so all the
//! borrow checks of `RefCell` apply to using `IcStorage` (e.g. trying to call `state.borrow_mut()`
//! when there is another borrow of the same type in scope will produce runtime panic).
//!
//! *IMPORTANT*: `IcStorage` only provides local canister state storage. It DOES NOT in any way
//! related to the stable storage. See [`crate::stable`] for stable storage.
//!
//! In order to preserve the canister data between canister upgrades you should
//! use either `ic_cdk::storage::stable_save()` and `ic_cdk::storage::stable_restore()`,
//! or [`crate::stable::read`] / [`crate::stable::write`].
//!
//! Any state that is not saved or restored using these methods will be lost if the canister
//! is upgraded.
//!
//! # Ways to use `IcStorage`
//!
//! There are two approaches for managing canister state:
//!
//! 1. You can have one big structure that contains all the state. In this case only this structure
//! must implement `IcStorage`. This approach makes it easier to take care of storing the state
//! in the stable storage on upgrade, and it's easier to manage the state when
//! it's all in one place. On the other hand, it means that you can only have one mutable
//! reference to this state during entire call, so you'll have to retrieve the state at the
//! call entry point, and then call all inner functions with the state reference as argument.
//! When there are many different parts of the state and they are not tightly connected to
//! each other, it can become somewhat cumbersome.
//!
//! 2. You can have different types implementing `IcStorage`, thus making them completely independent.
//! This approach is more flexible, especially for larger states, but special care must be taken
//! not to forget any part of the state in the upgrade methods. Reading and writing to and from
//! stable storage would require that everything that should be saved is passed as a single data
//! type (e.g a tuple or a custom struct), as writing to stable storage overwrites what is
//! currently there (meaning if we write one struct and then another, the second would overwrite
//! the first).
//!
//! # Testing
//!
//! When running unit tests sometimes more than one state is needed to simulate different canister
//! instances. For that, `ic-storage` macros generate a little different code for architectures
//! other then `wasm32`. In this case you need to use `ic_exports::ic_kit` to set up the
//! `MockingContext` and set the current `id` in that context. For each `id` different storage will
//! be returned by `IcStorage::get()` method even in the same test case.
use std::cell::RefCell;
use std::rc::Rc;
pub use ic_storage_derive::IcStorage;
pub mod error;
pub mod stable;
pub use error::{Error, Result};
pub mod testing;
/// Type that is stored in local canister state.
pub trait IcStorage {
/// Returns the reference to the canister state. `RefCell` is used to prevent memory corruption
/// for the state is the same object for all calls.
fn get() -> Rc<RefCell<Self>>;
}
#[macro_export]
macro_rules! generic_derive {
($storage:ty) => {
#[cfg(target_family = "wasm")]
// Note, that this path is not qualified because we have a derivation of IcStorage in
// `ic-metrics` which creates a looping dependency hell.
impl IcStorage for $storage {
fn get() -> ::std::rc::Rc<::std::cell::RefCell<Self>> {
use ::std::rc::Rc;
use ::std::cell::RefCell;
thread_local! {
static store: Rc<RefCell<$storage>> = Rc::new(RefCell::new(<$storage>::default()));
}
store.with(|v| v.clone())
}
}
#[cfg(not(target_family = "wasm"))]
impl IcStorage for $storage {
fn get() -> ::std::rc::Rc<::std::cell::RefCell<Self>> {
use ::std::rc::Rc;
use ::std::cell::RefCell;
use ::std::collections::HashMap;
use ::ic_exports::candid::Principal;
thread_local! {
static store: RefCell<HashMap<Principal, Rc<RefCell<$storage>>>> = RefCell::new(HashMap::default());
}
let id = ::ic_exports::ic_kit::ic::id();
store.with(|v| {
let mut borrowed_store = v.borrow_mut();
(*borrowed_store.entry(id).or_default()).clone()
})
}
}
}
}