Skip to main content

ic_testkit/pic/
errors.rs

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