1use candid::{CandidType, Principal, decode_one, encode_args, utils::ArgumentEncoder};
2use serde::de::DeserializeOwned;
3
4use super::{Pic, PicCallError};
5
6#[derive(Clone, Copy)]
7struct CallContext<'a> {
8 operation: &'static str,
9 canister_id: Principal,
10 caller: Principal,
11 method: &'a str,
12}
13
14impl Pic {
15 pub fn update_call<T, A>(
17 &self,
18 canister_id: Principal,
19 method: &str,
20 args: A,
21 ) -> Result<T, PicCallError>
22 where
23 T: CandidType + DeserializeOwned,
24 A: ArgumentEncoder,
25 {
26 self.update_call_as(canister_id, Principal::anonymous(), method, args)
27 }
28
29 #[track_caller]
34 pub fn update_call_or_panic<T, A>(&self, canister_id: Principal, method: &str, args: A) -> T
35 where
36 T: CandidType + DeserializeOwned,
37 A: ArgumentEncoder,
38 {
39 self.update_call(canister_id, method, args)
40 .unwrap_or_else(|err| panic!("{err}"))
41 }
42
43 pub fn update_call_as<T, A>(
45 &self,
46 canister_id: Principal,
47 caller: Principal,
48 method: &str,
49 args: A,
50 ) -> Result<T, PicCallError>
51 where
52 T: CandidType + DeserializeOwned,
53 A: ArgumentEncoder,
54 {
55 let context = CallContext {
56 operation: "update_call",
57 canister_id,
58 caller,
59 method,
60 };
61 let bytes = encode_call_args(args, context)?;
62 let result = self
63 .inner
64 .update_call(canister_id, caller, method, bytes)
65 .map_err(|err| {
66 PicCallError::new(format!(
67 "pocket_ic update_call failed (canister={canister_id}, caller={caller}, method={method}): {err}"
68 ))
69 })?;
70
71 decode_call_result(&result, context)
72 }
73
74 #[track_caller]
80 pub fn update_call_as_or_panic<T, A>(
81 &self,
82 canister_id: Principal,
83 caller: Principal,
84 method: &str,
85 args: A,
86 ) -> T
87 where
88 T: CandidType + DeserializeOwned,
89 A: ArgumentEncoder,
90 {
91 self.update_call_as(canister_id, caller, method, args)
92 .unwrap_or_else(|err| panic!("{err}"))
93 }
94
95 pub fn query_call<T, A>(
97 &self,
98 canister_id: Principal,
99 method: &str,
100 args: A,
101 ) -> Result<T, PicCallError>
102 where
103 T: CandidType + DeserializeOwned,
104 A: ArgumentEncoder,
105 {
106 self.query_call_as(canister_id, Principal::anonymous(), method, args)
107 }
108
109 #[track_caller]
114 pub fn query_call_or_panic<T, A>(&self, canister_id: Principal, method: &str, args: A) -> T
115 where
116 T: CandidType + DeserializeOwned,
117 A: ArgumentEncoder,
118 {
119 self.query_call(canister_id, method, args)
120 .unwrap_or_else(|err| panic!("{err}"))
121 }
122
123 pub fn query_call_as<T, A>(
125 &self,
126 canister_id: Principal,
127 caller: Principal,
128 method: &str,
129 args: A,
130 ) -> Result<T, PicCallError>
131 where
132 T: CandidType + DeserializeOwned,
133 A: ArgumentEncoder,
134 {
135 let context = CallContext {
136 operation: "query_call",
137 canister_id,
138 caller,
139 method,
140 };
141 let bytes = encode_call_args(args, context)?;
142 let result = self
143 .inner
144 .query_call(canister_id, caller, method, bytes)
145 .map_err(|err| {
146 PicCallError::new(format!(
147 "pocket_ic query_call failed (canister={canister_id}, caller={caller}, method={method}): {err}"
148 ))
149 })?;
150
151 decode_call_result(&result, context)
152 }
153
154 #[track_caller]
160 pub fn query_call_as_or_panic<T, A>(
161 &self,
162 canister_id: Principal,
163 caller: Principal,
164 method: &str,
165 args: A,
166 ) -> T
167 where
168 T: CandidType + DeserializeOwned,
169 A: ArgumentEncoder,
170 {
171 self.query_call_as(canister_id, caller, method, args)
172 .unwrap_or_else(|err| panic!("{err}"))
173 }
174
175 pub fn tick_n(&self, times: usize) {
177 for _ in 0..times {
178 self.tick();
179 }
180 }
181}
182
183fn encode_call_args<A>(args: A, context: CallContext<'_>) -> Result<Vec<u8>, PicCallError>
184where
185 A: ArgumentEncoder,
186{
187 encode_args(args).map_err(|err| {
188 PicCallError::new(format!(
189 "candid encode_args failed (operation={}, canister={}, caller={}, method={}): {err}",
190 context.operation, context.canister_id, context.caller, context.method
191 ))
192 })
193}
194
195fn decode_call_result<T>(result: &[u8], context: CallContext<'_>) -> Result<T, PicCallError>
196where
197 T: CandidType + DeserializeOwned,
198{
199 decode_one(result).map_err(|err| {
200 PicCallError::new(format!(
201 "candid decode_one failed (operation={}, canister={}, caller={}, method={}, bytes={}): {err}",
202 context.operation,
203 context.canister_id,
204 context.caller,
205 context.method,
206 result.len()
207 ))
208 })
209}
210
211#[cfg(test)]
212mod tests {
213 use candid::Principal;
214
215 use super::{CallContext, decode_call_result};
216
217 #[test]
218 fn decode_error_includes_call_context() {
219 let context = CallContext {
220 operation: "query_call",
221 canister_id: Principal::anonymous(),
222 caller: Principal::management_canister(),
223 method: "get",
224 };
225
226 let err = decode_call_result::<u64>(&[0xde, 0xad], context).expect_err("decode fails");
227
228 assert!(err.message.contains("candid decode_one failed"));
229 assert!(err.message.contains("operation=query_call"));
230 assert!(err.message.contains("method=get"));
231 assert!(err.message.contains("bytes=2"));
232 }
233}