pub trait Reasonable {
fn message(&self) -> &str;
}
pub trait Pausable<Reason: Reasonable> {
fn pause_query(&self) -> &Option<Reason>;
fn pause_replace(&mut self, reason: Option<Reason>);
fn pause_is_paused(&self) -> bool {
self.pause_query().is_some()
}
fn pause_is_running(&self) -> bool {
!self.pause_is_paused()
}
fn pause_must_be_running(&self) -> Result<(), String> {
if let Some(reason) = &self.pause_query() {
return Err(format!("Canister is paused: {}", reason.message()));
}
Ok(())
}
fn pause_must_be_paused(&self) -> Result<(), String> {
if self.pause_is_running() {
return Err("Canister is running. Not paused.".into());
}
Ok(())
}
}
pub mod basic {
use std::fmt::Display;
use candid::CandidType;
use serde::{Deserialize, Serialize};
use crate::{
functions::types::{Pausable, Reasonable},
types::TimestampNanos,
};
#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
pub struct PauseReason {
#[serde(alias = "timestamp_nanos")]
pub paused_at: TimestampNanos,
pub message: String,
}
impl Display for PauseReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!("{:?}", self))
}
}
impl std::error::Error for PauseReason {}
impl Reasonable for PauseReason {
fn message(&self) -> &str {
&self.message
}
}
impl PauseReason {
pub fn new(message: String) -> Self {
PauseReason {
paused_at: crate::times::now(),
message,
}
}
}
#[derive(CandidType, Serialize, Deserialize, Debug, Clone, Default)]
pub struct Pause(Option<PauseReason>);
impl Pausable<PauseReason> for Pause {
fn pause_query(&self) -> &Option<PauseReason> {
&self.0
}
fn pause_replace(&mut self, reason: Option<PauseReason>) {
self.0 = reason;
}
}
#[cfg(test)]
mod tests {
use ciborium::value::Value;
use serde::Serialize;
use super::PauseReason;
use crate::types::TimestampNanos;
#[derive(Serialize)]
struct LegacyPauseReason {
timestamp_nanos: TimestampNanos,
message: String,
}
#[test]
fn deserializes_legacy_pause_time_and_serializes_current_name() {
let legacy = LegacyPauseReason {
timestamp_nanos: TimestampNanos::from(42),
message: "maintenance".to_string(),
};
let mut legacy_cbor = Vec::new();
ciborium::ser::into_writer(&legacy, &mut legacy_cbor).unwrap();
let decoded: PauseReason = ciborium::de::from_reader(legacy_cbor.as_slice()).unwrap();
assert_eq!(decoded.paused_at, TimestampNanos::from(42));
let mut current_cbor = Vec::new();
ciborium::ser::into_writer(&decoded, &mut current_cbor).unwrap();
let current: Value = ciborium::de::from_reader(current_cbor.as_slice()).unwrap();
let Value::Map(entries) = current else {
panic!("expected a CBOR map")
};
let keys: Vec<&str> = entries
.iter()
.filter_map(|(key, _)| match key {
Value::Text(key) => Some(key.as_str()),
_ => None,
})
.collect();
assert!(keys.contains(&"paused_at"));
assert!(!keys.contains(&"timestamp_nanos"));
}
}
}