1use candid::Principal;
2use pocket_ic::{PocketIc, RejectResponse};
3
4use super::{PocketIcInstance, PocketIcOperationError};
5
6#[non_exhaustive]
10#[derive(Debug)]
11pub enum CandidCallError {
12 Encode {
14 context: Box<CandidCallContext>,
16 message: String,
18 },
19 Decode {
21 context: Box<CandidCallContext>,
23 bytes: usize,
25 source: Box<ic_testkit_candid::DecodeError>,
27 },
28 CanisterReject {
30 context: Box<CandidCallContext>,
32 response: Box<RejectResponse>,
34 },
35 Transport {
37 context: Box<CandidCallContext>,
39 source: PocketIcOperationError,
41 },
42}
43
44#[non_exhaustive]
46#[derive(Clone, Debug, Eq, PartialEq)]
47pub struct CandidCallContext {
48 pub operation: &'static str,
50 pub canister_id: Principal,
52 pub caller: Principal,
54 pub method: String,
56}
57
58#[non_exhaustive]
60#[derive(Debug, Eq, PartialEq)]
61pub enum CanisterInstallError {
62 CreateCanister {
64 label: Option<String>,
66 source: PocketIcOperationError,
68 },
69 AddCycles {
71 canister_id: Principal,
73 label: Option<String>,
75 source: PocketIcOperationError,
77 },
78 InstallCode {
80 canister_id: Principal,
82 label: Option<String>,
84 source: PocketIcOperationError,
86 },
87}
88
89pub struct StandaloneCanisterInstallError {
91 instance: Box<PocketIcInstance<PocketIc>>,
92 install_error: CanisterInstallError,
93}
94
95impl CandidCallContext {
96 #[must_use]
98 pub fn new(
99 operation: &'static str,
100 canister_id: Principal,
101 caller: Principal,
102 method: impl Into<String>,
103 ) -> Self {
104 Self {
105 operation,
106 canister_id,
107 caller,
108 method: method.into(),
109 }
110 }
111}
112
113impl std::fmt::Display for CandidCallError {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 let context = match self {
116 Self::Encode { context, message } => {
117 write!(f, "candid encode_args failed: {message}")?;
118 context
119 }
120 Self::Decode {
121 context,
122 bytes,
123 source,
124 } => {
125 write!(f, "candid decode failed (bytes={bytes}): {source}")?;
126 context
127 }
128 Self::CanisterReject { context, response } => {
129 write!(
130 f,
131 "pocket_ic {} was rejected: {response}",
132 context.operation
133 )?;
134 context
135 }
136 Self::Transport { context, source } => {
137 write!(f, "pocket_ic {} failed: {source}", context.operation)?;
138 context
139 }
140 };
141 write!(
142 f,
143 " (operation={}, canister={}, caller={}, method={})",
144 context.operation, context.canister_id, context.caller, context.method
145 )
146 }
147}
148
149impl std::error::Error for CandidCallError {
150 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
151 match self {
152 Self::Transport { source, .. } => Some(source),
153 Self::Decode { source, .. } => Some(source.as_ref()),
154 _ => None,
155 }
156 }
157}
158
159impl std::fmt::Display for CanisterInstallError {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 let (operation, canister_id, label, source) = match self {
162 Self::CreateCanister { label, source } => ("create_canister", None, label, source),
163 Self::AddCycles {
164 canister_id,
165 label,
166 source,
167 } => ("add_cycles", Some(canister_id), label, source),
168 Self::InstallCode {
169 canister_id,
170 label,
171 source,
172 } => ("install_canister", Some(canister_id), label, source),
173 };
174 write!(f, "{operation} failed")?;
175 if let Some(canister_id) = canister_id {
176 write!(f, " for canister {canister_id}")?;
177 }
178 if let Some(label) = label {
179 write!(f, " ({label})")?;
180 }
181 write!(f, ": {source}")
182 }
183}
184
185impl std::error::Error for CanisterInstallError {
186 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
187 let (Self::CreateCanister { source, .. }
188 | Self::AddCycles { source, .. }
189 | Self::InstallCode { source, .. }) = self;
190 Some(source)
191 }
192}
193
194impl StandaloneCanisterInstallError {
195 pub(super) fn new(
196 instance: PocketIcInstance<PocketIc>,
197 install_error: CanisterInstallError,
198 ) -> Self {
199 Self {
200 instance: Box::new(instance),
201 install_error,
202 }
203 }
204
205 #[must_use]
207 pub fn pocket_ic(&self) -> &PocketIc {
208 &self.instance.instance
209 }
210
211 #[must_use]
213 pub const fn install_error(&self) -> &CanisterInstallError {
214 &self.install_error
215 }
216
217 pub fn into_parts(self) -> (PocketIcInstance<PocketIc>, CanisterInstallError) {
219 (*self.instance, self.install_error)
220 }
221}
222
223impl std::fmt::Debug for StandaloneCanisterInstallError {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 f.debug_struct("StandaloneCanisterInstallError")
226 .field("install_error", &self.install_error)
227 .finish_non_exhaustive()
228 }
229}
230
231impl std::fmt::Display for StandaloneCanisterInstallError {
232 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 self.install_error.fmt(f)
234 }
235}
236
237impl std::error::Error for StandaloneCanisterInstallError {
238 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
239 Some(&self.install_error)
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use candid::Principal;
246
247 use super::CanisterInstallError;
248
249 #[test]
250 fn labeled_install_error_display_includes_label() {
251 let err = CanisterInstallError::InstallCode {
252 canister_id: Principal::anonymous(),
253 label: Some("authority".into()),
254 source: super::PocketIcOperationError::new("trap"),
255 };
256 assert!(err.to_string().contains("(authority): trap"));
257 }
258}