canic_core/ops/storage/state/
app.rs

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