canic/ops/model/memory/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::model::memory::MemoryOpsError,
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        MemoryOpsError::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
94    fn reset_state(mode: AppMode) {
95        AppStateOps::import(AppStateData { mode });
96    }
97
98    #[test]
99    fn command_changes_modes() {
100        reset_state(AppMode::Disabled);
101
102        assert!(AppStateOps::command(AppCommand::Start).is_ok());
103        assert_eq!(AppStateOps::get_mode(), AppMode::Enabled);
104
105        assert!(AppStateOps::command(AppCommand::Readonly).is_ok());
106        assert_eq!(AppStateOps::get_mode(), AppMode::Readonly);
107
108        assert!(AppStateOps::command(AppCommand::Stop).is_ok());
109        assert_eq!(AppStateOps::get_mode(), AppMode::Disabled);
110    }
111
112    #[test]
113    fn duplicate_command_fails() {
114        reset_state(AppMode::Enabled);
115
116        let err = AppStateOps::command(AppCommand::Start)
117            .unwrap_err()
118            .to_string();
119
120        assert!(
121            err.contains("app is already in Enabled mode"),
122            "unexpected error: {err}"
123        );
124    }
125}