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#[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, message } => {
137 write!(f, "pocket_ic {} failed: {message}", 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
151impl std::fmt::Display for CanisterInstallError {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 let (operation, canister_id, label, source) = match self {
154 Self::CreateCanister { label, source } => ("create_canister", None, label, source),
155 Self::AddCycles {
156 canister_id,
157 label,
158 source,
159 } => ("add_cycles", Some(canister_id), label, source),
160 Self::InstallCode {
161 canister_id,
162 label,
163 source,
164 } => ("install_canister", Some(canister_id), label, source),
165 };
166 write!(f, "{operation} failed")?;
167 if let Some(canister_id) = canister_id {
168 write!(f, " for canister {canister_id}")?;
169 }
170 if let Some(label) = label {
171 write!(f, " ({label})")?;
172 }
173 write!(f, ": {source}")
174 }
175}
176
177impl std::error::Error for CanisterInstallError {
178 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
179 let (Self::CreateCanister { source, .. }
180 | Self::AddCycles { source, .. }
181 | Self::InstallCode { source, .. }) = self;
182 Some(source)
183 }
184}
185
186impl StandaloneCanisterInstallError {
187 pub(super) fn new(
188 instance: PocketIcInstance<PocketIc>,
189 install_error: CanisterInstallError,
190 ) -> Self {
191 Self {
192 instance: Box::new(instance),
193 install_error,
194 }
195 }
196
197 #[must_use]
199 pub fn pocket_ic(&self) -> &PocketIc {
200 &self.instance.instance
201 }
202
203 #[must_use]
205 pub const fn install_error(&self) -> &CanisterInstallError {
206 &self.install_error
207 }
208
209 pub fn into_parts(self) -> (PocketIcInstance<PocketIc>, CanisterInstallError) {
211 (*self.instance, self.install_error)
212 }
213}
214
215impl std::fmt::Debug for StandaloneCanisterInstallError {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 f.debug_struct("StandaloneCanisterInstallError")
218 .field("install_error", &self.install_error)
219 .finish_non_exhaustive()
220 }
221}
222
223impl std::fmt::Display for StandaloneCanisterInstallError {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 self.install_error.fmt(f)
226 }
227}
228
229impl std::error::Error for StandaloneCanisterInstallError {
230 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
231 Some(&self.install_error)
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use candid::Principal;
238
239 use super::CanisterInstallError;
240
241 #[test]
242 fn labeled_install_error_display_includes_label() {
243 let err = CanisterInstallError::InstallCode {
244 canister_id: Principal::anonymous(),
245 label: Some("authority".into()),
246 source: super::PocketIcOperationError::new("trap"),
247 };
248 assert!(err.to_string().contains("(authority): trap"));
249 }
250}