canic_core/ops/storage/state/
app.rs

1pub use crate::model::memory::state::AppMode;
2
3use crate::{
4    Error, ThisError, log,
5    log::Topic,
6    model::memory::state::{AppState, AppStateData},
7    ops::storage::StorageOpsError,
8};
9use candid::CandidType;
10use derive_more::Display;
11use serde::Deserialize;
12
13///
14/// AppStateOpsError
15///
16
17#[derive(Debug, ThisError)]
18pub enum AppStateOpsError {
19    #[error("app is already in {0} mode")]
20    AlreadyInMode(AppMode),
21}
22
23impl From<AppStateOpsError> for Error {
24    fn from(err: AppStateOpsError) -> Self {
25        StorageOpsError::from(err).into()
26    }
27}
28
29///
30/// AppCommand
31///
32
33#[derive(CandidType, Clone, Copy, Debug, Deserialize, Display, Eq, PartialEq)]
34pub enum AppCommand {
35    Start,
36    Readonly,
37    Stop,
38}
39
40///
41/// AppStateOps
42///
43
44pub struct AppStateOps;
45
46impl AppStateOps {
47    #[must_use]
48    pub fn get_mode() -> AppMode {
49        AppState::get_mode()
50    }
51
52    pub fn set_mode(mode: AppMode) {
53        AppState::set_mode(mode);
54    }
55
56    pub fn command(cmd: AppCommand) -> Result<(), Error> {
57        let old_mode = AppState::get_mode();
58
59        let new_mode = match cmd {
60            AppCommand::Start => AppMode::Enabled,
61            AppCommand::Readonly => AppMode::Readonly,
62            AppCommand::Stop => AppMode::Disabled,
63        };
64
65        if old_mode == new_mode {
66            return Err(AppStateOpsError::AlreadyInMode(old_mode))?;
67        }
68
69        AppState::set_mode(new_mode);
70
71        log!(Topic::App, Ok, "app: mode changed {old_mode} -> {new_mode}");
72
73        Ok(())
74    }
75
76    pub fn import(data: AppStateData) {
77        AppState::import(data);
78    }
79
80    #[must_use]
81    pub fn export() -> AppStateData {
82        AppState::export()
83    }
84}
85
86///
87/// TESTS
88///
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::config::Config;
94
95    fn reset_state(mode: AppMode) {
96        Config::reset_for_tests();
97        let _ = Config::init_for_tests();
98        AppStateOps::import(AppStateData { mode });
99    }
100
101    #[test]
102    fn command_changes_modes() {
103        reset_state(AppMode::Disabled);
104
105        assert!(AppStateOps::command(AppCommand::Start).is_ok());
106        assert_eq!(AppStateOps::get_mode(), AppMode::Enabled);
107
108        assert!(AppStateOps::command(AppCommand::Readonly).is_ok());
109        assert_eq!(AppStateOps::get_mode(), AppMode::Readonly);
110
111        assert!(AppStateOps::command(AppCommand::Stop).is_ok());
112        assert_eq!(AppStateOps::get_mode(), AppMode::Disabled);
113    }
114
115    #[test]
116    fn duplicate_command_fails() {
117        reset_state(AppMode::Enabled);
118
119        let err = AppStateOps::command(AppCommand::Start)
120            .unwrap_err()
121            .to_string();
122
123        assert!(
124            err.contains("app is already in Enabled mode"),
125            "unexpected error: {err}"
126        );
127    }
128}