1use candid::Principal;
2use pocket_ic::{PocketIc, RejectResponse};
3
4use super::{PocketIcInstance, PocketIcOperationError};
5
6#[non_exhaustive]
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub enum CandidCallError {
12 Encode {
14 context: Box<CandidCallContext>,
16 message: String,
18 },
19 Decode {
21 context: Box<CandidCallContext>,
23 bytes: usize,
25 message: String,
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 message,
124 } => {
125 write!(f, "candid decode failed (bytes={bytes}): {message}")?;
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 _ => None,
154 }
155 }
156}
157
158impl std::fmt::Display for CanisterInstallError {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 let (operation, canister_id, label, source) = match self {
161 Self::CreateCanister { label, source } => ("create_canister", None, label, source),
162 Self::AddCycles {
163 canister_id,
164 label,
165 source,
166 } => ("add_cycles", Some(canister_id), label, source),
167 Self::InstallCode {
168 canister_id,
169 label,
170 source,
171 } => ("install_canister", Some(canister_id), label, source),
172 };
173 write!(f, "{operation} failed")?;
174 if let Some(canister_id) = canister_id {
175 write!(f, " for canister {canister_id}")?;
176 }
177 if let Some(label) = label {
178 write!(f, " ({label})")?;
179 }
180 write!(f, ": {source}")
181 }
182}
183
184impl std::error::Error for CanisterInstallError {
185 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
186 let (Self::CreateCanister { source, .. }
187 | Self::AddCycles { source, .. }
188 | Self::InstallCode { source, .. }) = self;
189 Some(source)
190 }
191}
192
193impl StandaloneCanisterInstallError {
194 pub(super) fn new(
195 instance: PocketIcInstance<PocketIc>,
196 install_error: CanisterInstallError,
197 ) -> Self {
198 Self {
199 instance: Box::new(instance),
200 install_error,
201 }
202 }
203
204 #[must_use]
206 pub fn pocket_ic(&self) -> &PocketIc {
207 &self.instance.instance
208 }
209
210 #[must_use]
212 pub const fn install_error(&self) -> &CanisterInstallError {
213 &self.install_error
214 }
215
216 pub fn into_parts(self) -> (PocketIcInstance<PocketIc>, CanisterInstallError) {
218 (*self.instance, self.install_error)
219 }
220}
221
222impl std::fmt::Debug for StandaloneCanisterInstallError {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 f.debug_struct("StandaloneCanisterInstallError")
225 .field("install_error", &self.install_error)
226 .finish_non_exhaustive()
227 }
228}
229
230impl std::fmt::Display for StandaloneCanisterInstallError {
231 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
232 self.install_error.fmt(f)
233 }
234}
235
236impl std::error::Error for StandaloneCanisterInstallError {
237 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
238 Some(&self.install_error)
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use candid::Principal;
245
246 use super::CanisterInstallError;
247
248 #[test]
249 fn labeled_install_error_display_includes_label() {
250 let err = CanisterInstallError::InstallCode {
251 canister_id: Principal::anonymous(),
252 label: Some("authority".into()),
253 source: super::PocketIcOperationError::new("trap"),
254 };
255 assert!(err.to_string().contains("(authority): trap"));
256 }
257}