canic_core/model/memory/state/
app.rs

1use crate::{
2    cdk::structures::{DefaultMemoryImpl, cell::Cell, memory::VirtualMemory},
3    eager_static, ic_memory, impl_storable_bounded,
4    model::memory::id::state::APP_STATE_ID,
5};
6use candid::CandidType;
7use derive_more::Display;
8use serde::{Deserialize, Serialize};
9use std::cell::RefCell;
10
11//
12// APP_STATE
13//
14
15eager_static! {
16    static APP_STATE: RefCell<Cell<AppStateData, VirtualMemory<DefaultMemoryImpl>>> =
17        RefCell::new(Cell::init(
18            ic_memory!(AppState, APP_STATE_ID),
19            AppStateData::default(),
20        ));
21}
22
23///
24/// AppMode
25/// used for the query/update guards
26/// Eventually we'll have more granularity overall
27///
28
29#[derive(
30    CandidType, Clone, Copy, Debug, Default, Display, Eq, PartialEq, Serialize, Deserialize,
31)]
32pub enum AppMode {
33    Enabled,
34    Readonly,
35    #[default]
36    Disabled,
37}
38
39///
40/// AppStateData
41///
42
43#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
44pub struct AppStateData {
45    pub mode: AppMode,
46}
47
48impl_storable_bounded!(AppStateData, 32, true);
49
50///
51/// AppState
52///
53
54pub struct AppState;
55
56impl AppState {
57    #[must_use]
58    pub fn get_mode() -> AppMode {
59        APP_STATE.with_borrow(|cell| cell.get().mode)
60    }
61
62    pub fn set_mode(mode: AppMode) {
63        APP_STATE.with_borrow_mut(|cell| {
64            let mut data = *cell.get();
65            data.mode = mode;
66            cell.set(data);
67        });
68    }
69
70    pub fn import(data: AppStateData) {
71        APP_STATE.with_borrow_mut(|cell| cell.set(data));
72    }
73
74    #[must_use]
75    pub fn export() -> AppStateData {
76        APP_STATE.with_borrow(|cell| *cell.get())
77    }
78}
79
80///
81/// TESTS
82///
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    fn reset_state(mode: AppMode) {
89        AppState::import(AppStateData { mode });
90    }
91
92    #[test]
93    fn default_mode_is_disabled() {
94        reset_state(AppMode::Disabled);
95        assert_eq!(AppState::get_mode(), AppMode::Disabled);
96    }
97
98    #[test]
99    fn can_set_mode() {
100        reset_state(AppMode::Disabled);
101
102        AppState::set_mode(AppMode::Enabled);
103        assert_eq!(AppState::get_mode(), AppMode::Enabled);
104
105        AppState::set_mode(AppMode::Readonly);
106        assert_eq!(AppState::get_mode(), AppMode::Readonly);
107    }
108
109    #[test]
110    fn import_and_export_state() {
111        reset_state(AppMode::Disabled);
112
113        let data = AppStateData {
114            mode: AppMode::Readonly,
115        };
116        AppState::import(data);
117
118        assert_eq!(AppState::export().mode, AppMode::Readonly);
119
120        // After export we can reuse
121        let exported = AppState::export();
122        assert_eq!(exported, data);
123    }
124}