1use candid::Principal;
2
3use super::{PicSerialGuardError, startup::PicStartError};
4
5#[non_exhaustive]
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub struct PicCallError {
12 pub message: String,
13 pub kind: PicCallErrorKind,
14 pub context: Option<Box<PicCallContext>>,
15}
16
17#[non_exhaustive]
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum PicCallErrorKind {
20 Encode,
21 Decode,
22 Transport,
23 Other,
24}
25
26#[non_exhaustive]
27#[derive(Clone, Debug, Eq, PartialEq)]
28pub struct PicCallContext {
29 pub operation: &'static str,
30 pub canister_id: Principal,
31 pub caller: Principal,
32 pub method: String,
33}
34
35#[derive(Debug, Eq, PartialEq)]
40pub struct PicInstallError {
41 canister_id: Principal,
42 label: Option<String>,
43 message: String,
44}
45
46#[derive(Debug)]
51pub enum StandaloneCanisterFixtureError {
52 SerialGuard(PicSerialGuardError),
53 Start(PicStartError),
54 Install(PicInstallError),
55}
56
57impl PicCallContext {
58 #[must_use]
60 pub fn new(
61 operation: &'static str,
62 canister_id: Principal,
63 caller: Principal,
64 method: impl Into<String>,
65 ) -> Self {
66 Self {
67 operation,
68 canister_id,
69 caller,
70 method: method.into(),
71 }
72 }
73
74 #[must_use]
76 pub const fn operation(&self) -> &'static str {
77 self.operation
78 }
79
80 #[must_use]
82 pub const fn canister_id(&self) -> Principal {
83 self.canister_id
84 }
85
86 #[must_use]
88 pub const fn caller(&self) -> Principal {
89 self.caller
90 }
91
92 #[must_use]
94 pub fn method(&self) -> &str {
95 &self.method
96 }
97}
98
99impl PicCallError {
100 #[must_use]
102 pub fn new(message: impl Into<String>) -> Self {
103 Self {
104 message: message.into(),
105 kind: PicCallErrorKind::Other,
106 context: None,
107 }
108 }
109
110 #[must_use]
112 pub fn encode(context: PicCallContext, source: impl std::fmt::Display) -> Self {
113 let message = format!(
114 "candid encode_args failed (operation={}, canister={}, caller={}, method={}): {source}",
115 context.operation, context.canister_id, context.caller, context.method
116 );
117
118 Self {
119 message,
120 kind: PicCallErrorKind::Encode,
121 context: Some(Box::new(context)),
122 }
123 }
124
125 #[must_use]
127 pub fn decode(context: PicCallContext, bytes: usize, source: impl std::fmt::Display) -> Self {
128 let message = format!(
129 "candid decode_one failed (operation={}, canister={}, caller={}, method={}, bytes={}): {source}",
130 context.operation, context.canister_id, context.caller, context.method, bytes
131 );
132
133 Self {
134 message,
135 kind: PicCallErrorKind::Decode,
136 context: Some(Box::new(context)),
137 }
138 }
139
140 #[must_use]
142 pub fn transport(context: PicCallContext, source: impl std::fmt::Display) -> Self {
143 let message = format!(
144 "pocket_ic {} failed (canister={}, caller={}, method={}): {source}",
145 context.operation, context.canister_id, context.caller, context.method
146 );
147
148 Self {
149 message,
150 kind: PicCallErrorKind::Transport,
151 context: Some(Box::new(context)),
152 }
153 }
154
155 #[must_use]
157 pub fn message(&self) -> &str {
158 &self.message
159 }
160
161 #[must_use]
163 pub const fn kind(&self) -> PicCallErrorKind {
164 self.kind
165 }
166
167 #[must_use]
169 pub fn context(&self) -> Option<&PicCallContext> {
170 self.context.as_deref()
171 }
172}
173
174impl std::fmt::Display for PicCallError {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 f.write_str(&self.message)
177 }
178}
179
180impl std::error::Error for PicCallError {}
181
182impl PicInstallError {
183 #[must_use]
185 pub const fn new(canister_id: Principal, message: String) -> Self {
186 Self {
187 canister_id,
188 label: None,
189 message,
190 }
191 }
192
193 #[must_use]
195 pub fn labeled(
196 canister_id: Principal,
197 label: impl Into<String>,
198 message: impl Into<String>,
199 ) -> Self {
200 Self {
201 canister_id,
202 label: Some(label.into()),
203 message: message.into(),
204 }
205 }
206
207 #[must_use]
209 pub const fn canister_id(&self) -> Principal {
210 self.canister_id
211 }
212
213 #[must_use]
215 pub fn message(&self) -> &str {
216 &self.message
217 }
218
219 #[must_use]
221 pub fn label(&self) -> Option<&str> {
222 self.label.as_deref()
223 }
224}
225
226impl std::fmt::Display for PicInstallError {
227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228 if let Some(label) = &self.label {
229 write!(
230 f,
231 "failed to install canister {} ({label}): {}",
232 self.canister_id, self.message
233 )
234 } else {
235 write!(
236 f,
237 "failed to install canister {}: {}",
238 self.canister_id, self.message
239 )
240 }
241 }
242}
243
244impl std::error::Error for PicInstallError {}
245
246impl std::fmt::Display for StandaloneCanisterFixtureError {
247 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248 match self {
249 Self::SerialGuard(err) => write!(f, "{err}"),
250 Self::Start(err) => write!(f, "{err}"),
251 Self::Install(err) => write!(f, "{err}"),
252 }
253 }
254}
255
256impl std::error::Error for StandaloneCanisterFixtureError {
257 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
258 match self {
259 Self::SerialGuard(err) => Some(err),
260 Self::Start(err) => Some(err),
261 Self::Install(err) => Some(err),
262 }
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use candid::Principal;
269
270 use super::PicInstallError;
271
272 #[test]
273 fn labeled_install_error_display_includes_label() {
274 let err = PicInstallError::labeled(Principal::anonymous(), "authority", "trap");
275
276 assert_eq!(err.label(), Some("authority"));
277 assert!(err.to_string().contains("(authority): trap"));
278 }
279}