ic_canister_kit/functions/
pausable.rs1pub trait Reasonable {
7 fn message(&self) -> &str;
9}
10
11pub trait Pausable<Reason: Reasonable> {
13 fn pause_query(&self) -> &Option<Reason>;
17
18 fn pause_replace(&mut self, reason: Option<Reason>);
22
23 fn pause_is_paused(&self) -> bool {
27 self.pause_query().is_some()
28 }
29 fn pause_is_running(&self) -> bool {
31 !self.pause_is_paused()
32 }
33 fn pause_must_be_running(&self) -> Result<(), String> {
35 if let Some(reason) = &self.pause_query() {
36 return Err(format!("Canister is paused: {}", reason.message()));
37 }
38 Ok(())
39 }
40 fn pause_must_be_paused(&self) -> Result<(), String> {
42 if self.pause_is_running() {
43 return Err("Canister is running. Not paused.".into());
44 }
45 Ok(())
46 }
47}
48
49pub mod basic {
53 use std::fmt::Display;
54
55 use candid::CandidType;
56 use serde::{Deserialize, Serialize};
57
58 use crate::{
59 functions::types::{Pausable, Reasonable},
60 types::TimestampNanos,
61 };
62
63 #[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
65 pub struct PauseReason {
66 #[serde(alias = "timestamp_nanos")]
68 pub paused_at: TimestampNanos,
69
70 pub message: String,
72 }
73
74 impl Display for PauseReason {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 f.write_str(&format!("{:?}", self))
77 }
78 }
79
80 impl std::error::Error for PauseReason {}
81
82 impl Reasonable for PauseReason {
83 fn message(&self) -> &str {
84 &self.message
85 }
86 }
87
88 impl PauseReason {
89 pub fn new(message: String) -> Self {
91 PauseReason {
92 paused_at: crate::times::now(),
93 message,
94 }
95 }
96 }
97
98 #[derive(CandidType, Serialize, Deserialize, Debug, Clone, Default)]
100 pub struct Pause(Option<PauseReason>);
101
102 impl Pausable<PauseReason> for Pause {
103 fn pause_query(&self) -> &Option<PauseReason> {
105 &self.0
106 }
107 fn pause_replace(&mut self, reason: Option<PauseReason>) {
110 self.0 = reason;
111 }
112 }
113
114 #[cfg(test)]
115 mod tests {
116 use ciborium::value::Value;
117 use serde::Serialize;
118
119 use super::PauseReason;
120 use crate::types::TimestampNanos;
121
122 #[derive(Serialize)]
123 struct LegacyPauseReason {
124 timestamp_nanos: TimestampNanos,
125 message: String,
126 }
127
128 #[test]
129 fn deserializes_legacy_pause_time_and_serializes_current_name() {
130 let legacy = LegacyPauseReason {
131 timestamp_nanos: TimestampNanos::from(42),
132 message: "maintenance".to_string(),
133 };
134 let mut legacy_cbor = Vec::new();
135 ciborium::ser::into_writer(&legacy, &mut legacy_cbor).unwrap();
136 let decoded: PauseReason = ciborium::de::from_reader(legacy_cbor.as_slice()).unwrap();
137 assert_eq!(decoded.paused_at, TimestampNanos::from(42));
138
139 let mut current_cbor = Vec::new();
140 ciborium::ser::into_writer(&decoded, &mut current_cbor).unwrap();
141 let current: Value = ciborium::de::from_reader(current_cbor.as_slice()).unwrap();
142 let Value::Map(entries) = current else {
143 panic!("expected a CBOR map")
144 };
145 let keys: Vec<&str> = entries
146 .iter()
147 .filter_map(|(key, _)| match key {
148 Value::Text(key) => Some(key.as_str()),
149 _ => None,
150 })
151 .collect();
152 assert!(keys.contains(&"paused_at"));
153 assert!(!keys.contains(&"timestamp_nanos"));
154 }
155 }
156}