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.
38#[derive(thiserror::Error)]
39#[error("{install_error}")]
40pub struct StandaloneCanisterInstallError {
41    instance: Box<PocketIcInstance<PocketIc>>,
42    #[source]
43    install_error: CanisterInstallError,
44}
45
46impl std::fmt::Display for CanisterInstallError {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        let (operation, canister_id, label, source) = match self {
49            Self::CreateCanister { label, source } => ("create_canister", None, label, source),
50            Self::AddCycles {
51                canister_id,
52                label,
53                source,
54            } => ("add_cycles", Some(canister_id), label, source),
55            Self::InstallCode {
56                canister_id,
57                label,
58                source,
59            } => ("install_canister", Some(canister_id), label, source),
60        };
61        write!(f, "{operation} failed")?;
62        if let Some(canister_id) = canister_id {
63            write!(f, " for canister {canister_id}")?;
64        }
65        if let Some(label) = label {
66            write!(f, " ({label})")?;
67        }
68        write!(f, ": {source}")
69    }
70}
71
72impl std::error::Error for CanisterInstallError {
73    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
74        let (Self::CreateCanister { source, .. }
75        | Self::AddCycles { source, .. }
76        | Self::InstallCode { source, .. }) = self;
77        Some(source)
78    }
79}
80
81impl StandaloneCanisterInstallError {
82    pub(super) fn new(
83        instance: PocketIcInstance<PocketIc>,
84        install_error: CanisterInstallError,
85    ) -> Self {
86        Self {
87            instance: Box::new(instance),
88            install_error,
89        }
90    }
91
92    /// Borrow the caller-created instance retained after the failed install.
93    #[must_use]
94    pub fn pocket_ic(&self) -> &PocketIc {
95        self.instance.instance()
96    }
97
98    /// Inspect the structured install failure.
99    #[must_use]
100    pub const fn install_error(&self) -> &CanisterInstallError {
101        &self.install_error
102    }
103
104    /// Recover the instance/server ownership record and install failure.
105    pub fn into_parts(self) -> (PocketIcInstance<PocketIc>, CanisterInstallError) {
106        (*self.instance, self.install_error)
107    }
108}
109
110impl std::fmt::Debug for StandaloneCanisterInstallError {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        f.debug_struct("StandaloneCanisterInstallError")
113            .field("install_error", &self.install_error)
114            .finish_non_exhaustive()
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use ic_platform::principal::ExternalPrincipal as Principal;
121
122    use super::CanisterInstallError;
123
124    #[test]
125    fn labeled_install_error_display_includes_label() {
126        let err = CanisterInstallError::InstallCode {
127            canister_id: Principal::anonymous(),
128            label: Some("authority".into()),
129            source: super::PocketIcOperationError::new("trap"),
130        };
131        assert!(err.to_string().contains("(authority): trap"));
132    }
133}