1use crate::pocket_ic::ClientError;
2use crate::pocket_ic::PocketIc;
3use ic_platform_types::principal::ExternalPrincipal as Principal;
4
5use super::PocketIcInstance;
6
7#[non_exhaustive]
9#[derive(Debug)]
10pub enum CanisterInstallError {
11 CreateCanister {
13 label: Option<String>,
15 source: ClientError,
17 },
18 AddCycles {
20 canister_id: Principal,
22 label: Option<String>,
24 source: ClientError,
26 },
27 InstallCode {
29 canister_id: Principal,
31 label: Option<String>,
33 source: ClientError,
35 },
36}
37
38#[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 #[must_use]
95 pub fn pocket_ic(&self) -> &PocketIc {
96 self.instance.instance()
97 }
98
99 #[must_use]
101 pub const fn install_error(&self) -> &CanisterInstallError {
102 &self.install_error
103 }
104
105 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 crate::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}