1use ic_platform::pocket_ic::PocketIc;
2use ic_platform::principal::ExternalPrincipal as Principal;
3
4use super::{PocketIcInstance, PocketIcOperationError};
5
6#[non_exhaustive]
8#[derive(Debug, Eq, PartialEq)]
9pub enum CanisterInstallError {
10 CreateCanister {
12 label: Option<String>,
14 source: PocketIcOperationError,
16 },
17 AddCycles {
19 canister_id: Principal,
21 label: Option<String>,
23 source: PocketIcOperationError,
25 },
26 InstallCode {
28 canister_id: Principal,
30 label: Option<String>,
32 source: PocketIcOperationError,
34 },
35}
36
37#[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 #[must_use]
94 pub fn pocket_ic(&self) -> &PocketIc {
95 self.instance.instance()
96 }
97
98 #[must_use]
100 pub const fn install_error(&self) -> &CanisterInstallError {
101 &self.install_error
102 }
103
104 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}