1use crate::call::{AsyncCaller, SyncCaller};
2use candid::utils::ArgumentEncoder;
3use candid::{ser::IDLBuilder, types::value::IDLValue, utils::ArgumentDecoder, CandidType, Encode};
4use ic_agent::{export::Principal, Agent, AgentError, RequestId};
5use std::convert::TryInto;
6use thiserror::Error;
7
8#[derive(Debug, Error)]
10pub enum CanisterBuilderError {
11 #[error("Getting the Canister ID returned an error: {0}")]
13 PrincipalError(#[from] Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>),
14
15 #[error("Must specify an Agent")]
17 MustSpecifyAnAgent(),
18
19 #[error("Must specify a Canister ID")]
21 MustSpecifyCanisterId(),
22}
23
24#[derive(Debug, Default)]
26pub struct CanisterBuilder<'agent> {
27 agent: Option<&'agent Agent>,
28 canister_id: Option<Result<Principal, CanisterBuilderError>>,
29}
30
31impl<'agent> CanisterBuilder<'agent> {
32 pub fn new() -> CanisterBuilder<'static> {
34 Default::default()
35 }
36
37 pub fn with_canister_id<E, P>(self, canister_id: P) -> Self
39 where
40 E: 'static + std::error::Error + std::marker::Send + std::marker::Sync,
41 P: TryInto<Principal, Error = E>,
42 {
43 Self {
44 canister_id: Some(
45 canister_id
46 .try_into()
47 .map_err(|e| CanisterBuilderError::PrincipalError(Box::new(e))),
48 ),
49 ..self
50 }
51 }
52
53 pub fn with_agent(self, agent: &'agent Agent) -> Self {
55 CanisterBuilder {
56 agent: Some(agent),
57 ..self
58 }
59 }
60
61 pub fn build(self) -> Result<Canister<'agent>, CanisterBuilderError> {
63 let canister_id = if let Some(cid) = self.canister_id {
64 cid?
65 } else {
66 return Err(CanisterBuilderError::MustSpecifyCanisterId());
67 };
68
69 let agent = self
70 .agent
71 .ok_or(CanisterBuilderError::MustSpecifyAnAgent())?;
72 Ok(Canister { agent, canister_id })
73 }
74}
75
76#[derive(Debug, Clone)]
83pub struct Canister<'agent> {
84 pub(super) agent: &'agent Agent,
85 pub(super) canister_id: Principal,
86}
87
88impl<'agent> Canister<'agent> {
89 pub fn canister_id_(&self) -> &Principal {
92 &self.canister_id
93 }
94
95 pub fn canister_id(&self) -> &Principal {
97 &self.canister_id
98 }
99
100 pub fn update_<'canister>(
103 &'canister self,
104 method_name: &str,
105 ) -> AsyncCallBuilder<'agent, 'canister> {
106 AsyncCallBuilder::new(self, method_name)
107 }
108
109 pub fn update<'canister>(
111 &'canister self,
112 method_name: &str,
113 ) -> AsyncCallBuilder<'agent, 'canister> {
114 AsyncCallBuilder::new(self, method_name)
115 }
116
117 pub fn query_<'canister>(
120 &'canister self,
121 method_name: &str,
122 ) -> SyncCallBuilder<'agent, 'canister> {
123 SyncCallBuilder::new(self, method_name)
124 }
125
126 pub fn query<'canister>(
128 &'canister self,
129 method_name: &str,
130 ) -> SyncCallBuilder<'agent, 'canister> {
131 SyncCallBuilder::new(self, method_name)
132 }
133
134 pub async fn wait<'canister>(
136 &'canister self,
137 request_id: &RequestId,
138 ) -> Result<Vec<u8>, AgentError> {
139 self.agent
140 .wait(request_id, self.canister_id)
141 .await
142 .map(|x| x.0)
143 }
144
145 pub fn clone_with_(&self, id: Principal) -> Self {
148 Self {
149 agent: self.agent,
150 canister_id: id,
151 }
152 }
153 pub fn clone_with(&self, id: Principal) -> Self {
155 Self {
156 agent: self.agent,
157 canister_id: id,
158 }
159 }
160
161 pub fn builder() -> CanisterBuilder<'agent> {
163 Default::default()
164 }
165}
166
167#[derive(Debug, Default)]
169pub struct Argument(pub(crate) Option<Result<Vec<u8>, AgentError>>);
170
171impl Argument {
172 pub fn set_idl_arg<A: CandidType>(&mut self, arg: A) {
174 match self.0 {
175 None => self.0 = Some(Encode!(&arg).map_err(|e| e.into())),
176 Some(_) => panic!("argument is being set more than once"),
177 }
178 }
179
180 pub fn set_value_arg(&mut self, arg: IDLValue) {
182 match self.0 {
183 None => {
184 let mut builder = IDLBuilder::new();
185 let result = builder
186 .value_arg(&arg)
187 .and_then(|builder| builder.serialize_to_vec())
188 .map_err(Into::into);
189 self.0 = Some(result);
190 }
191 Some(_) => panic!("argument is being set more than once"),
192 }
193 }
194
195 pub fn set_raw_arg(&mut self, arg: Vec<u8>) {
197 match self.0 {
198 None => self.0 = Some(Ok(arg)),
199 Some(_) => panic!("argument is being set more than once"),
200 }
201 }
202
203 pub fn serialize(self) -> Result<Vec<u8>, AgentError> {
205 self.0.unwrap_or_else(|| Ok(Encode!()?))
206 }
207
208 pub fn reset(&mut self) {
210 *self = Default::default();
211 }
212
213 pub fn new() -> Self {
215 Default::default()
216 }
217
218 pub fn from_raw(raw: Vec<u8>) -> Self {
220 Self(Some(Ok(raw)))
221 }
222
223 pub fn from_candid(tuple: impl ArgumentEncoder) -> Self {
225 let mut builder = IDLBuilder::new();
226 let result = tuple
227 .encode(&mut builder)
228 .and_then(|_| builder.serialize_to_vec())
229 .map_err(Into::into);
230 Self(Some(result))
231 }
232}
233
234#[derive(Debug)]
238pub struct SyncCallBuilder<'agent, 'canister> {
239 canister: &'canister Canister<'agent>,
240 method_name: String,
241 effective_canister_id: Principal,
242 arg: Argument,
243}
244
245impl<'agent: 'canister, 'canister> SyncCallBuilder<'agent, 'canister> {
246 pub(super) fn new<M: Into<String>>(
248 canister: &'canister Canister<'agent>,
249 method_name: M,
250 ) -> Self {
251 Self {
252 canister,
253 method_name: method_name.into(),
254 effective_canister_id: canister.canister_id().to_owned(),
255 arg: Default::default(),
256 }
257 }
258}
259
260impl<'agent: 'canister, 'canister> SyncCallBuilder<'agent, 'canister> {
261 pub fn with_arg<Argument>(mut self, arg: Argument) -> Self
263 where
264 Argument: CandidType + Sync + Send,
265 {
266 self.arg.set_idl_arg(arg);
267 self
268 }
269 pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
271 assert!(self.arg.0.is_none(), "argument is being set more than once");
272 self.arg = Argument::from_candid(tuple);
273 self
274 }
275
276 pub fn with_value_arg(mut self, arg: IDLValue) -> Self {
280 self.arg.set_value_arg(arg);
281 self
282 }
283
284 pub fn with_arg_raw(mut self, arg: Vec<u8>) -> Self {
286 self.arg.set_raw_arg(arg);
287 self
288 }
289
290 pub fn with_effective_canister_id(mut self, canister_id: Principal) -> Self {
292 self.effective_canister_id = canister_id;
293 self
294 }
295
296 pub fn build<Output>(self) -> SyncCaller<'agent, Output>
298 where
299 Output: for<'de> ArgumentDecoder<'de> + Send + Sync,
300 {
301 let c = self.canister;
302 SyncCaller {
303 agent: c.agent,
304 effective_canister_id: self.effective_canister_id,
305 canister_id: c.canister_id,
306 method_name: self.method_name.clone(),
307 arg: self.arg.serialize(),
308 expiry: Default::default(),
309 phantom_out: std::marker::PhantomData,
310 }
311 }
312}
313
314#[derive(Debug)]
318pub struct AsyncCallBuilder<'agent, 'canister> {
319 canister: &'canister Canister<'agent>,
320 method_name: String,
321 effective_canister_id: Principal,
322 arg: Argument,
323}
324
325impl<'agent: 'canister, 'canister> AsyncCallBuilder<'agent, 'canister> {
326 pub(super) fn new(
328 canister: &'canister Canister<'agent>,
329 method_name: &str,
330 ) -> AsyncCallBuilder<'agent, 'canister> {
331 Self {
332 canister,
333 method_name: method_name.to_string(),
334 effective_canister_id: canister.canister_id().to_owned(),
335 arg: Default::default(),
336 }
337 }
338}
339
340impl<'agent: 'canister, 'canister> AsyncCallBuilder<'agent, 'canister> {
341 pub fn with_arg<Argument>(mut self, arg: Argument) -> Self
343 where
344 Argument: CandidType + Sync + Send,
345 {
346 self.arg.set_idl_arg(arg);
347 self
348 }
349 pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
351 assert!(self.arg.0.is_none(), "argument is being set more than once");
352 self.arg = Argument::from_candid(tuple);
353 self
354 }
355
356 pub fn with_arg_raw(mut self, arg: Vec<u8>) -> Self {
358 self.arg.set_raw_arg(arg);
359 self
360 }
361
362 pub fn with_effective_canister_id(mut self, canister_id: Principal) -> Self {
364 self.effective_canister_id = canister_id;
365 self
366 }
367
368 pub fn build<Output>(self) -> AsyncCaller<'agent, Output>
370 where
371 Output: for<'de> ArgumentDecoder<'de> + Send + Sync,
372 {
373 let c = self.canister;
374 AsyncCaller {
375 agent: c.agent,
376 effective_canister_id: self.effective_canister_id,
377 canister_id: c.canister_id,
378 method_name: self.method_name.clone(),
379 arg: self.arg.serialize(),
380 expiry: Default::default(),
381 phantom_out: std::marker::PhantomData,
382 }
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::super::interfaces::ManagementCanister;
389 use crate::call::AsyncCall;
390 use candid::Principal;
391 use ic_agent::identity::BasicIdentity;
392
393 const POCKET_IC: &str = "POCKET_IC";
394
395 async fn get_effective_canister_id() -> Principal {
396 let default_effective_canister_id =
397 Principal::from_text("rwlgt-iiaaa-aaaaa-aaaaa-cai").unwrap();
398 if let Ok(pocket_ic_url) = std::env::var(POCKET_IC) {
399 pocket_ic::nonblocking::get_default_effective_canister_id(pocket_ic_url)
400 .await
401 .unwrap_or(default_effective_canister_id)
402 } else {
403 default_effective_canister_id
404 }
405 }
406
407 #[ignore]
408 #[tokio::test]
409 async fn simple() {
410 use super::Canister;
411
412 let identity =
413 BasicIdentity::from_raw_key(&ic_ed25519::PrivateKey::generate().serialize_raw());
414
415 let port = std::env::var("IC_REF_PORT").unwrap_or_else(|_| "4943".into());
416
417 let agent = ic_agent::Agent::builder()
418 .with_url(format!("http://localhost:{port}"))
419 .with_identity(identity)
420 .build()
421 .unwrap();
422 agent.fetch_root_key().await.unwrap();
423
424 let management_canister = ManagementCanister::from_canister(
425 Canister::builder()
426 .with_agent(&agent)
427 .with_canister_id("aaaaa-aa")
428 .build()
429 .unwrap(),
430 );
431
432 let (new_canister_id,) = management_canister
433 .create_canister()
434 .as_provisional_create_with_amount(None)
435 .with_effective_canister_id(get_effective_canister_id().await)
436 .call_and_wait()
437 .await
438 .unwrap();
439
440 let (status,) = management_canister
441 .canister_status(&new_canister_id)
442 .call_and_wait()
443 .await
444 .unwrap();
445
446 assert_eq!(format!("{:?}", status.status), "Running");
447
448 let canister_wasm = b"\0asm\x01\0\0\0";
449 management_canister
450 .install_code(&new_canister_id, canister_wasm)
451 .call_and_wait()
452 .await
453 .unwrap();
454
455 let canister = Canister::builder()
456 .with_agent(&agent)
457 .with_canister_id(new_canister_id)
458 .build()
459 .unwrap();
460
461 assert!(canister
462 .update("hello")
463 .build::<()>()
464 .call_and_wait()
465 .await
466 .is_err());
467 }
468}