canic/ops/model/memory/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::model::memory::MemoryOpsError,
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 MemoryOpsError::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
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}