canic_core/ops/storage/state/
app.rs1pub 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#[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#[derive(CandidType, Clone, Copy, Debug, Deserialize, Display, Eq, PartialEq)]
34pub enum AppCommand {
35 Start,
36 Readonly,
37 Stop,
38}
39
40pub 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#[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}