canic_core/model/memory/state/
app.rs

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