Skip to main content

ic_testkit/pic/
errors.rs

1use ic_platform::pocket_ic::PocketIc;
2use ic_platform::principal::ExternalPrincipal as Principal;
3
4use super::{PocketIcInstance, PocketIcOperationError};
5
6/// Failed creation, funding, or installation with its original operation error.
7#[non_exhaustive]
8#[derive(Debug, Eq, PartialEq)]
9pub enum CanisterInstallError {
10    /// Canister creation failed before an id was returned.
11    CreateCanister {
12        /// Optional caller-provided install label.
13        label: Option<String>,
14        /// Original upstream operation failure.
15        source: PocketIcOperationError,
16    },
17    /// Cycle funding failed for the created canister.
18    AddCycles {
19        /// Created canister, retained for caller inspection.
20        canister_id: Principal,
21        /// Optional caller-provided install label.
22        label: Option<String>,
23        /// Original upstream operation failure.
24        source: PocketIcOperationError,
25    },
26    /// Wasm installation failed for the created canister.
27    InstallCode {
28        /// Created canister, retained for caller inspection.
29        canister_id: Principal,
30        /// Optional caller-provided install label.
31        label: Option<String>,
32        /// Original upstream operation failure.
33        source: PocketIcOperationError,
34    },
35}
36
37/// Failed standalone install retaining the caller-created instance and optional server.
38pub struct StandaloneCanisterInstallError {
39    instance: Box<PocketIcInstance<PocketIc>>,
40    install_error: CanisterInstallError,
41}
42
43impl std::fmt::Display for CanisterInstallError {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        let (operation, canister_id, label, source) = match self {
46            Self::CreateCanister { label, source } => ("create_canister", None, label, source),
47            Self::AddCycles {
48                canister_id,
49                label,
50                source,
51            } => ("add_cycles", Some(canister_id), label, source),
52            Self::InstallCode {
53                canister_id,
54                label,
55                source,
56            } => ("install_canister", Some(canister_id), label, source),
57        };
58        write!(f, "{operation} failed")?;
59        if let Some(canister_id) = canister_id {
60            write!(f, " for canister {canister_id}")?;
61        }
62        if let Some(label) = label {
63            write!(f, " ({label})")?;
64        }
65        write!(f, ": {source}")
66    }
67}
68
69impl std::error::Error for CanisterInstallError {
70    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
71        let (Self::CreateCanister { source, .. }
72        | Self::AddCycles { source, .. }
73        | Self::InstallCode { source, .. }) = self;
74        Some(source)
75    }
76}
77
78impl StandaloneCanisterInstallError {
79    pub(super) fn new(
80        instance: PocketIcInstance<PocketIc>,
81        install_error: CanisterInstallError,
82    ) -> Self {
83        Self {
84            instance: Box::new(instance),
85            install_error,
86        }
87    }
88
89    /// Borrow the caller-created instance retained after the failed install.
90    #[must_use]
91    pub fn pocket_ic(&self) -> &PocketIc {
92        &self.instance.instance
93    }
94
95    /// Inspect the structured install failure.
96    #[must_use]
97    pub const fn install_error(&self) -> &CanisterInstallError {
98        &self.install_error
99    }
100
101    /// Recover the instance/server ownership record and install failure.
102    pub fn into_parts(self) -> (PocketIcInstance<PocketIc>, CanisterInstallError) {
103        (*self.instance, self.install_error)
104    }
105}
106
107impl std::fmt::Debug for StandaloneCanisterInstallError {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        f.debug_struct("StandaloneCanisterInstallError")
110            .field("install_error", &self.install_error)
111            .finish_non_exhaustive()
112    }
113}
114
115impl std::fmt::Display for StandaloneCanisterInstallError {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        self.install_error.fmt(f)
118    }
119}
120
121impl std::error::Error for StandaloneCanisterInstallError {
122    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
123        Some(&self.install_error)
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use ic_platform::principal::ExternalPrincipal as Principal;
130
131    use super::CanisterInstallError;
132
133    #[test]
134    fn labeled_install_error_display_includes_label() {
135        let err = CanisterInstallError::InstallCode {
136            canister_id: Principal::anonymous(),
137            label: Some("authority".into()),
138            source: super::PocketIcOperationError::new("trap"),
139        };
140        assert!(err.to_string().contains("(authority): trap"));
141    }
142}