Skip to main content

ic_canister_kit/functions/
pausable.rs

1//! 维护状态
2
3// ================== 功能 ==================
4
5/// 维护原因
6pub trait Reasonable {
7    /// 维护原因
8    fn message(&self) -> &str;
9}
10
11/// 维护记录
12pub trait Pausable<Reason: Reasonable> {
13    // 查询
14
15    /// 查询维护状态
16    fn pause_query(&self) -> &Option<Reason>;
17
18    // 修改
19
20    /// 修改维护状态
21    fn pause_replace(&mut self, reason: Option<Reason>);
22
23    // 默认方法
24
25    /// 是否维护中
26    fn pause_is_paused(&self) -> bool {
27        self.pause_query().is_some()
28    }
29    /// 是否正常运行
30    fn pause_is_running(&self) -> bool {
31        !self.pause_is_paused()
32    }
33    /// 正常运行中才能继续
34    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    /// 维护中才能继续
41    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
49// ================== 简单实现 ==================
50
51/// 维护功能简单实现
52pub 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    /// 维护原因对象
64    #[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
65    pub struct PauseReason {
66        /// 进入维护状态的时间
67        #[serde(alias = "timestamp_nanos")]
68        pub paused_at: TimestampNanos,
69
70        /// 维护原因
71        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        /// 构造维护原因
90        pub fn new(message: String) -> Self {
91            PauseReason {
92                paused_at: crate::times::now(),
93                message,
94            }
95        }
96    }
97
98    /// 记录维护状态
99    #[derive(CandidType, Serialize, Deserialize, Debug, Clone, Default)]
100    pub struct Pause(Option<PauseReason>);
101
102    impl Pausable<PauseReason> for Pause {
103        // 查询
104        fn pause_query(&self) -> &Option<PauseReason> {
105            &self.0
106        }
107        // 修改
108        // 设置维护状态
109        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}