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 message: String,
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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub enum CanisterInstallPhase {
61 CreateCanister,
63 AddCycles,
65 InstallCode,
67}
68
69impl CanisterInstallPhase {
70 #[must_use]
72 pub const fn operation(self) -> &'static str {
73 match self {
74 Self::CreateCanister => "create_canister",
75 Self::AddCycles => "add_cycles",
76 Self::InstallCode => "install_canister",
77 }
78 }
79}
80
81#[derive(Debug, Eq, PartialEq)]
83pub struct CanisterInstallError {
84 phase: CanisterInstallPhase,
85 canister_id: Option<Principal>,
86 label: Option<String>,
87 source: PocketIcOperationError,
88}
89
90pub struct StandaloneCanisterInstallError {
92 instance: Box<PocketIcInstance<PocketIc>>,
93 install_error: CanisterInstallError,
94}
95
96impl CandidCallContext {
97 #[must_use]
99 pub fn new(
100 operation: &'static str,
101 canister_id: Principal,
102 caller: Principal,
103 method: impl Into<String>,
104 ) -> Self {
105 Self {
106 operation,
107 canister_id,
108 caller,
109 method: method.into(),
110 }
111 }
112}
113
114impl std::fmt::Display for CandidCallError {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 let context = match self {
117 Self::Encode { context, message } => {
118 write!(f, "candid encode_args failed: {message}")?;
119 context
120 }
121 Self::Decode {
122 context,
123 bytes,
124 message,
125 } => {
126 write!(f, "candid decode failed (bytes={bytes}): {message}")?;
127 context
128 }
129 Self::CanisterReject { context, response } => {
130 write!(
131 f,
132 "pocket_ic {} was rejected: {response}",
133 context.operation
134 )?;
135 context
136 }
137 Self::Transport { context, message } => {
138 write!(f, "pocket_ic {} failed: {message}", context.operation)?;
139 context
140 }
141 };
142 write!(
143 f,
144 " (operation={}, canister={}, caller={}, method={})",
145 context.operation, context.canister_id, context.caller, context.method
146 )
147 }
148}
149
150impl std::error::Error for CandidCallError {}
151
152impl CanisterInstallError {
153 #[must_use]
155 pub const fn new(
156 phase: CanisterInstallPhase,
157 canister_id: Option<Principal>,
158 label: Option<String>,
159 source: PocketIcOperationError,
160 ) -> Self {
161 Self {
162 phase,
163 canister_id,
164 label,
165 source,
166 }
167 }
168
169 #[must_use]
171 pub const fn phase(&self) -> CanisterInstallPhase {
172 self.phase
173 }
174
175 #[must_use]
177 pub const fn canister_id(&self) -> Option<Principal> {
178 self.canister_id
179 }
180
181 #[must_use]
183 pub fn message(&self) -> &str {
184 self.source.message()
185 }
186
187 #[must_use]
189 pub const fn operation_error(&self) -> &PocketIcOperationError {
190 &self.source
191 }
192
193 #[must_use]
195 pub fn label(&self) -> Option<&str> {
196 self.label.as_deref()
197 }
198}
199
200impl std::fmt::Display for CanisterInstallError {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 write!(f, "{} failed", self.phase.operation())?;
203 if let Some(canister_id) = self.canister_id {
204 write!(f, " for canister {canister_id}")?;
205 }
206 if let Some(label) = &self.label {
207 write!(f, " ({label})")?;
208 }
209 write!(f, ": {}", self.source)
210 }
211}
212
213impl std::error::Error for CanisterInstallError {
214 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
215 Some(&self.source)
216 }
217}
218
219impl StandaloneCanisterInstallError {
220 pub(super) fn new(
221 instance: PocketIcInstance<PocketIc>,
222 install_error: CanisterInstallError,
223 ) -> Self {
224 Self {
225 instance: Box::new(instance),
226 install_error,
227 }
228 }
229
230 #[must_use]
232 pub fn pocket_ic(&self) -> &PocketIc {
233 &self.instance.instance
234 }
235
236 #[must_use]
238 pub const fn install_error(&self) -> &CanisterInstallError {
239 &self.install_error
240 }
241
242 pub fn into_parts(self) -> (PocketIcInstance<PocketIc>, CanisterInstallError) {
244 (*self.instance, self.install_error)
245 }
246}
247
248impl std::fmt::Debug for StandaloneCanisterInstallError {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250 f.debug_struct("StandaloneCanisterInstallError")
251 .field("install_error", &self.install_error)
252 .finish_non_exhaustive()
253 }
254}
255
256impl std::fmt::Display for StandaloneCanisterInstallError {
257 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258 self.install_error.fmt(f)
259 }
260}
261
262impl std::error::Error for StandaloneCanisterInstallError {
263 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
264 Some(&self.install_error)
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use candid::Principal;
271
272 use super::CanisterInstallError;
273
274 #[test]
275 fn labeled_install_error_display_includes_label() {
276 let err = CanisterInstallError::new(
277 super::CanisterInstallPhase::InstallCode,
278 Some(Principal::anonymous()),
279 Some("authority".into()),
280 super::PocketIcOperationError::new("trap"),
281 );
282
283 assert_eq!(err.label(), Some("authority"));
284 assert!(err.to_string().contains("(authority): trap"));
285 }
286}