ic_cdk/api/call.rs
1//! APIs to make and manage calls in the canister.
2use crate::api::trap;
3use candid::utils::{ArgumentDecoder, ArgumentEncoder};
4use candid::{decode_args, encode_args, write_args, CandidType, Deserialize, Principal};
5use serde::ser::Error;
6use std::future::Future;
7use std::marker::PhantomData;
8use std::pin::Pin;
9use std::sync::atomic::Ordering;
10use std::sync::{Arc, RwLock, Weak};
11use std::task::{Context, Poll, Waker};
12
13/// Rejection code from calling another canister.
14///
15/// These can be obtained either using `reject_code()` or `reject_result()`.
16#[allow(missing_docs)]
17#[repr(i32)]
18#[derive(CandidType, Deserialize, Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)]
19pub enum RejectionCode {
20 NoError = 0,
21
22 SysFatal = 1,
23 SysTransient = 2,
24 DestinationInvalid = 3,
25 CanisterReject = 4,
26 CanisterError = 5,
27
28 Unknown,
29}
30
31impl From<i32> for RejectionCode {
32 fn from(code: i32) -> Self {
33 match code {
34 0 => RejectionCode::NoError,
35 1 => RejectionCode::SysFatal,
36 2 => RejectionCode::SysTransient,
37 3 => RejectionCode::DestinationInvalid,
38 4 => RejectionCode::CanisterReject,
39 5 => RejectionCode::CanisterError,
40 _ => RejectionCode::Unknown,
41 }
42 }
43}
44
45impl From<u32> for RejectionCode {
46 fn from(code: u32) -> Self {
47 RejectionCode::from(code as i32)
48 }
49}
50
51/// The result of a Call.
52///
53/// Errors on the IC have two components; a Code and a message associated with it.
54pub type CallResult<R> = Result<R, (RejectionCode, String)>;
55
56// Internal state for the Future when sending a call.
57struct CallFutureState<T: AsRef<[u8]>> {
58 result: Option<CallResult<Vec<u8>>>,
59 waker: Option<Waker>,
60 id: Principal,
61 method: String,
62 arg: T,
63 payment: u128,
64}
65
66struct CallFuture<T: AsRef<[u8]>> {
67 state: Arc<RwLock<CallFutureState<T>>>,
68}
69
70impl<T: AsRef<[u8]>> Future for CallFuture<T> {
71 type Output = CallResult<Vec<u8>>;
72
73 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
74 let self_ref = Pin::into_inner(self);
75 let mut state = self_ref.state.write().unwrap();
76
77 if let Some(result) = state.result.take() {
78 Poll::Ready(result)
79 } else {
80 if state.waker.is_none() {
81 let callee = state.id.as_slice();
82 let method = &state.method;
83 let args = state.arg.as_ref();
84 let payment = state.payment;
85 let state_ptr = Weak::into_raw(Arc::downgrade(&self_ref.state));
86 // SAFETY:
87 // `callee`, being &[u8], is a readable sequence of bytes and therefore can be passed to ic0.call_new.
88 // `method`, being &str, is a readable sequence of bytes and therefore can be passed to ic0.call_new.
89 // `callback` is a function with signature (env : i32) -> () and therefore can be called as both reply and reject fn for ic0.call_new.
90 // `state_ptr` is a pointer created via Weak::into_raw, and can therefore be passed as the userdata for `callback`.
91 // `args`, being a &[u8], is a readable sequence of bytes and therefore can be passed to ic0.call_data_append.
92 // `cleanup` is a function with signature (env : i32) -> () and therefore can be called as a cleanup fn for ic0.call_on_cleanup.
93 // `state_ptr` is a pointer created via Weak::into_raw, and can therefore be passed as the userdata for `cleanup`.
94 // ic0.call_perform is always safe to call.
95 // callback and cleanup are safe to parameterize with T because:
96 // - if the future is dropped before the callback is called, there will be no more strong references and the weak reference will fail to upgrade
97 // - if the future is *not* dropped before the callback is called, the compiler will mandate that any data borrowed by T is still alive
98 let err_code = unsafe {
99 ic0::call_new(
100 callee.as_ptr() as i32,
101 callee.len() as i32,
102 method.as_ptr() as i32,
103 method.len() as i32,
104 callback::<T> as usize as i32,
105 state_ptr as i32,
106 callback::<T> as usize as i32,
107 state_ptr as i32,
108 );
109
110 ic0::call_data_append(args.as_ptr() as i32, args.len() as i32);
111 add_payment(payment);
112 ic0::call_on_cleanup(cleanup::<T> as usize as i32, state_ptr as i32);
113 ic0::call_perform()
114 };
115
116 // 0 is a special error code meaning call succeeded.
117 if err_code != 0 {
118 let result = Err((
119 RejectionCode::from(err_code),
120 "Couldn't send message".to_string(),
121 ));
122 state.result = Some(result.clone());
123 return Poll::Ready(result);
124 }
125 }
126 state.waker = Some(context.waker().clone());
127 Poll::Pending
128 }
129 }
130}
131
132/// The callback from IC dereferences the future from a raw pointer, assigns the
133/// result and calls the waker. We cannot use a closure here because we pass raw
134/// pointers to the System and back.
135///
136/// # Safety
137///
138/// This function must only be passed to the IC with a pointer from Weak::into_raw as userdata.
139unsafe extern "C" fn callback<T: AsRef<[u8]>>(state_ptr: *const RwLock<CallFutureState<T>>) {
140 // SAFETY: This function is only ever called by the IC, and we only ever pass a Weak as userdata.
141 let state = unsafe { Weak::from_raw(state_ptr) };
142 if let Some(state) = state.upgrade() {
143 // Make sure to un-borrow_mut the state.
144 {
145 state.write().unwrap().result = Some(match reject_code() {
146 RejectionCode::NoError => Ok(arg_data_raw()),
147 n => Err((n, reject_message())),
148 });
149 }
150 let w = state.write().unwrap().waker.take();
151 if let Some(waker) = w {
152 // This is all to protect this little guy here which will call the poll() which
153 // borrow_mut() the state as well. So we need to be careful to not double-borrow_mut.
154 waker.wake()
155 }
156 }
157}
158
159/// This function is called when [callback] was just called with the same parameter, and trapped.
160/// We can't guarantee internal consistency at this point, but we can at least e.g. drop mutex guards.
161/// Waker is a very opaque API, so the best we can do is set a global flag and proceed normally.
162///
163/// # Safety
164///
165/// This function must only be passed to the IC with a pointer from Weak::into_raw as userdata.
166unsafe extern "C" fn cleanup<T: AsRef<[u8]>>(state_ptr: *const RwLock<CallFutureState<T>>) {
167 // SAFETY: This function is only ever called by the IC, and we only ever pass a Weak as userdata.
168 let state = unsafe { Weak::from_raw(state_ptr) };
169 if let Some(state) = state.upgrade() {
170 // We set the call result, even though it won't be read on the
171 // default executor, because we can't guarantee it was called on
172 // our executor. However, we are not allowed to inspect
173 // reject_code() inside of a cleanup callback, so always set the
174 // result to a reject.
175 //
176 // Borrowing does not trap - the rollback from the
177 // previous trap ensures that the RwLock can be borrowed again.
178 state.write().unwrap().result = Some(Err((RejectionCode::NoError, "cleanup".to_string())));
179 let w = state.write().unwrap().waker.take();
180 if let Some(waker) = w {
181 // Flag that we do not want to actually wake the task - we
182 // want to drop it *without* executing it.
183 ic_cdk_executor::CLEANUP.store(true, Ordering::Relaxed);
184 waker.wake();
185 ic_cdk_executor::CLEANUP.store(false, Ordering::Relaxed);
186 }
187 }
188}
189
190fn add_payment(payment: u128) {
191 if payment == 0 {
192 return;
193 }
194 let high = (payment >> 64) as u64;
195 let low = (payment & u64::MAX as u128) as u64;
196 // SAFETY: ic0.call_cycles_add128 is always safe to call.
197 unsafe {
198 ic0::call_cycles_add128(high as i64, low as i64);
199 }
200}
201
202/// Sends a one-way message with `payment` cycles attached to it that invokes `method` with
203/// arguments `args` on the principal identified by `id`, ignoring the reply.
204///
205/// Returns `Ok(())` if the message was successfully enqueued, otherwise returns a reject code.
206///
207/// # Notes
208///
209/// * The caller has no way of checking whether the destination processed the notification.
210/// The system can drop the notification if the destination does not have resources to
211/// process the message (for example, if it's out of cycles or queue slots).
212///
213/// * The callee cannot tell whether the call is one-way or not.
214/// The callee must produce replies for all incoming messages.
215///
216/// * It is safe to upgrade a canister without stopping it first if it sends out *only*
217/// one-way messages.
218///
219/// * If the payment is non-zero and the system fails to deliver the notification, the behaviour
220/// is unspecified: the funds can be either reimbursed or consumed irrevocably by the IC depending
221/// on the underlying implementation of one-way calls.
222pub fn notify_with_payment128<T: ArgumentEncoder>(
223 id: Principal,
224 method: &str,
225 args: T,
226 payment: u128,
227) -> Result<(), RejectionCode> {
228 let args_raw = encode_args(args).expect("failed to encode arguments");
229 notify_raw(id, method, &args_raw, payment)
230}
231
232/// Like [notify_with_payment128], but sets the payment to zero.
233pub fn notify<T: ArgumentEncoder>(
234 id: Principal,
235 method: &str,
236 args: T,
237) -> Result<(), RejectionCode> {
238 notify_with_payment128(id, method, args, 0)
239}
240
241/// Like [notify], but sends the argument as raw bytes, skipping Candid serialization.
242pub fn notify_raw(
243 id: Principal,
244 method: &str,
245 args_raw: &[u8],
246 payment: u128,
247) -> Result<(), RejectionCode> {
248 let callee = id.as_slice();
249 // We set all callbacks to -1, which is guaranteed to be invalid callback index.
250 // The system will still deliver the reply, but it will trap immediately because the callback
251 // is not a valid function. See
252 // https://www.joachim-breitner.de/blog/789-Zero-downtime_upgrades_of_Internet_Computer_canisters#one-way-calls
253 // for more context.
254
255 // SAFETY:
256 // `callee`, being &[u8], is a readable sequence of bytes and therefore can be passed to ic0.call_new.
257 // `method`, being &str, is a readable sequence of bytes and therefore can be passed to ic0.call_new.
258 // -1, i.e. usize::MAX, is a function pointer the wasm module cannot possibly contain, and therefore can be passed as both reply and reject fn for ic0.call_new.
259 // Since the callback function will never be called, any value can be passed as its context parameter, and therefore -1 can be passed for those values.
260 // `args`, being a &[u8], is a readable sequence of bytes and therefore can be passed to ic0.call_data_append.
261 // ic0.call_perform is always safe to call.
262 let err_code = unsafe {
263 ic0::call_new(
264 callee.as_ptr() as i32,
265 callee.len() as i32,
266 method.as_ptr() as i32,
267 method.len() as i32,
268 /* reply_fun = */ -1,
269 /* reply_env = */ -1,
270 /* reject_fun = */ -1,
271 /* reject_env = */ -1,
272 );
273 add_payment(payment);
274 ic0::call_data_append(args_raw.as_ptr() as i32, args_raw.len() as i32);
275 ic0::call_perform()
276 };
277 match err_code {
278 0 => Ok(()),
279 c => Err(RejectionCode::from(c)),
280 }
281}
282
283/// Similar to `call`, but without serialization.
284pub fn call_raw<'a, T: AsRef<[u8]> + Send + Sync + 'a>(
285 id: Principal,
286 method: &str,
287 args_raw: T,
288 payment: u64,
289) -> impl Future<Output = CallResult<Vec<u8>>> + Send + Sync + 'a {
290 call_raw_internal(id, method, args_raw, payment.into())
291}
292
293/// Similar to `call128`, but without serialization.
294pub fn call_raw128<'a, T: AsRef<[u8]> + Send + Sync + 'a>(
295 id: Principal,
296 method: &str,
297 args_raw: T,
298 payment: u128,
299) -> impl Future<Output = CallResult<Vec<u8>>> + Send + Sync + 'a {
300 call_raw_internal(id, method, args_raw, payment)
301}
302
303fn call_raw_internal<'a, T: AsRef<[u8]> + Send + Sync + 'a>(
304 id: Principal,
305 method: &str,
306 args_raw: T,
307 payment: u128,
308) -> impl Future<Output = CallResult<Vec<u8>>> + Send + Sync + 'a {
309 let state = Arc::new(RwLock::new(CallFutureState {
310 result: None,
311 waker: None,
312 id,
313 method: method.to_string(),
314 arg: args_raw,
315 payment,
316 }));
317 CallFuture { state }
318}
319
320fn decoder_error_to_reject<T>(err: candid::error::Error) -> (RejectionCode, String) {
321 (
322 RejectionCode::CanisterError,
323 format!(
324 "failed to decode canister response as {}: {}",
325 std::any::type_name::<T>(),
326 err
327 ),
328 )
329}
330
331/// Performs an asynchronous call to another canister using the [System API](https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-call).
332///
333/// If the reply payload is not a valid encoding of the expected type `T`,
334/// the call results in [RejectionCode::CanisterError] error.
335///
336/// Note that the asynchronous call must be awaited
337/// in order for the inter-canister call to be made
338/// using the [System API](https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-call).
339pub fn call<T: ArgumentEncoder, R: for<'a> ArgumentDecoder<'a>>(
340 id: Principal,
341 method: &str,
342 args: T,
343) -> impl Future<Output = CallResult<R>> + Send + Sync {
344 let args_raw = encode_args(args).expect("Failed to encode arguments.");
345 let fut = call_raw(id, method, args_raw, 0);
346 async {
347 let bytes = fut.await?;
348 decode_args(&bytes).map_err(decoder_error_to_reject::<R>)
349 }
350}
351
352/// Performs an asynchronous call to another canister and pay cycles at the same time.
353///
354/// Note that the asynchronous call must be awaited
355/// in order for the inter-canister call to be made
356/// using the [System API](https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-call).
357pub fn call_with_payment<T: ArgumentEncoder, R: for<'a> ArgumentDecoder<'a>>(
358 id: Principal,
359 method: &str,
360 args: T,
361 cycles: u64,
362) -> impl Future<Output = CallResult<R>> + Send + Sync {
363 let args_raw = encode_args(args).expect("Failed to encode arguments.");
364 let fut = call_raw(id, method, args_raw, cycles);
365 async {
366 let bytes = fut.await?;
367 decode_args(&bytes).map_err(decoder_error_to_reject::<R>)
368 }
369}
370
371/// Performs an asynchronous call to another canister and pay cycles at the same time.
372///
373/// Note that the asynchronous call must be awaited
374/// in order for the inter-canister call to be made
375/// using the [System API](https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-call).
376pub fn call_with_payment128<T: ArgumentEncoder, R: for<'a> ArgumentDecoder<'a>>(
377 id: Principal,
378 method: &str,
379 args: T,
380 cycles: u128,
381) -> impl Future<Output = CallResult<R>> + Send + Sync {
382 let args_raw = encode_args(args).expect("Failed to encode arguments.");
383 let fut = call_raw128(id, method, args_raw, cycles);
384 async {
385 let bytes = fut.await?;
386 decode_args(&bytes).map_err(decoder_error_to_reject::<R>)
387 }
388}
389
390/// Returns a result that maps over the call
391///
392/// It will be Ok(T) if the call succeeded (with T being the arg_data),
393/// and [reject_message()] if it failed.
394pub fn result<T: for<'a> ArgumentDecoder<'a>>() -> Result<T, String> {
395 match reject_code() {
396 RejectionCode::NoError => {
397 decode_args(&arg_data_raw()).map_err(|e| format!("Failed to decode arguments: {}", e))
398 }
399 _ => Err(reject_message()),
400 }
401}
402
403/// Returns the rejection code for the call.
404pub fn reject_code() -> RejectionCode {
405 // SAFETY: ic0.msg_reject_code is always safe to call.
406 let code = unsafe { ic0::msg_reject_code() };
407 RejectionCode::from(code)
408}
409
410/// Returns the rejection message.
411pub fn reject_message() -> String {
412 // SAFETY: ic0.msg_reject_msg_size is always safe to call.
413 let len: u32 = unsafe { ic0::msg_reject_msg_size() as u32 };
414 let mut bytes = vec![0u8; len as usize];
415 // SAFETY: `bytes`, being mutable and allocated to `len` bytes, is safe to pass to ic0.msg_reject_msg_copy with no offset
416 unsafe {
417 ic0::msg_reject_msg_copy(bytes.as_mut_ptr() as i32, 0, len as i32);
418 }
419 String::from_utf8_lossy(&bytes).into_owned()
420}
421
422/// Rejects the current call with the message.
423pub fn reject(message: &str) {
424 let err_message = message.as_bytes();
425 // SAFETY: `err_message`, being &[u8], is a readable sequence of bytes, and therefore valid to pass to ic0.msg_reject.
426 unsafe {
427 ic0::msg_reject(err_message.as_ptr() as i32, err_message.len() as i32);
428 }
429}
430
431/// An io::Write for message replies.
432#[derive(Debug, Copy, Clone)]
433pub struct CallReplyWriter;
434
435impl std::io::Write for CallReplyWriter {
436 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
437 // SAFETY: buf, being &[u8], is a readable sequence of bytes, and therefore valid to pass to ic0.msg_reply_data_append.
438 unsafe {
439 ic0::msg_reply_data_append(buf.as_ptr() as i32, buf.len() as i32);
440 }
441 Ok(buf.len())
442 }
443
444 fn flush(&mut self) -> std::io::Result<()> {
445 Ok(())
446 }
447}
448
449/// Replies to the current call with a candid argument.
450pub fn reply<T: ArgumentEncoder>(reply: T) {
451 write_args(&mut CallReplyWriter, reply).expect("Could not encode reply.");
452 // SAFETY: ic0.msg_reply is always safe to call.
453 unsafe {
454 ic0::msg_reply();
455 }
456}
457
458/// Returns the amount of cycles that were transferred by the caller
459/// of the current call, and is still available in this message.
460pub fn msg_cycles_available() -> u64 {
461 // SAFETY: ic0.msg_cycles_available is always safe to call.
462 unsafe { ic0::msg_cycles_available() as u64 }
463}
464
465/// Returns the amount of cycles that were transferred by the caller
466/// of the current call, and is still available in this message.
467pub fn msg_cycles_available128() -> u128 {
468 let mut recv = 0u128;
469 // SAFETY: recv is writable and sixteen bytes wide, and therefore is safe to pass to ic0.msg_cycles_available128
470 unsafe {
471 ic0::msg_cycles_available128(&mut recv as *mut u128 as i32);
472 }
473 recv
474}
475
476/// Returns the amount of cycles that came back with the response as a refund.
477///
478/// The refund has already been added to the canister balance automatically.
479pub fn msg_cycles_refunded() -> u64 {
480 // SAFETY: ic0.msg_cycles_refunded is always safe to call
481 unsafe { ic0::msg_cycles_refunded() as u64 }
482}
483
484/// Returns the amount of cycles that came back with the response as a refund.
485///
486/// The refund has already been added to the canister balance automatically.
487pub fn msg_cycles_refunded128() -> u128 {
488 let mut recv = 0u128;
489 // SAFETY: recv is writable and sixteen bytes wide, and therefore is safe to pass to ic0.msg_cycles_refunded128
490 unsafe {
491 ic0::msg_cycles_refunded128(&mut recv as *mut u128 as i32);
492 }
493 recv
494}
495
496/// Moves cycles from the call to the canister balance.
497///
498/// The actual amount moved will be returned.
499pub fn msg_cycles_accept(max_amount: u64) -> u64 {
500 // SAFETY: ic0.msg_cycles_accept is always safe to call.
501 unsafe { ic0::msg_cycles_accept(max_amount as i64) as u64 }
502}
503
504/// Moves cycles from the call to the canister balance.
505///
506/// The actual amount moved will be returned.
507pub fn msg_cycles_accept128(max_amount: u128) -> u128 {
508 let high = (max_amount >> 64) as u64;
509 let low = (max_amount & u64::MAX as u128) as u64;
510 let mut recv = 0u128;
511 // SAFETY: `recv` is writable and sixteen bytes wide, and therefore safe to pass to ic0.msg_cycles_accept128
512 unsafe {
513 ic0::msg_cycles_accept128(high as i64, low as i64, &mut recv as *mut u128 as i32);
514 }
515 recv
516}
517
518/// Returns the argument data as bytes.
519pub fn arg_data_raw() -> Vec<u8> {
520 // SAFETY: ic0.msg_arg_data_size is always safe to call.
521 let len: usize = unsafe { ic0::msg_arg_data_size() as usize };
522 let mut bytes = Vec::with_capacity(len);
523 // SAFETY:
524 // `bytes`, being mutable and allocated to `len` bytes, is safe to pass to ic0.msg_arg_data_copy with no offset
525 // ic0.msg_arg_data_copy writes to all of `bytes[0..len]`, so `set_len` is safe to call with the new len.
526 unsafe {
527 ic0::msg_arg_data_copy(bytes.as_mut_ptr() as i32, 0, len as i32);
528 bytes.set_len(len);
529 }
530 bytes
531}
532
533/// Get the len of the raw-argument-data-bytes.
534pub fn arg_data_raw_size() -> usize {
535 // SAFETY: ic0.msg_arg_data_size is always safe to call.
536 unsafe { ic0::msg_arg_data_size() as usize }
537}
538
539/// Replies with the bytes passed
540pub fn reply_raw(buf: &[u8]) {
541 if !buf.is_empty() {
542 // SAFETY: `buf`, being &[u8], is a readable sequence of bytes, and therefore valid to pass to ic0.msg_reject.
543 unsafe { ic0::msg_reply_data_append(buf.as_ptr() as i32, buf.len() as i32) }
544 };
545 // SAFETY: ic0.msg_reply is always safe to call.
546 unsafe { ic0::msg_reply() };
547}
548
549/// Returns the argument data in the current call. Traps if the data cannot be
550/// decoded.
551pub fn arg_data<R: for<'a> ArgumentDecoder<'a>>() -> R {
552 let bytes = arg_data_raw();
553
554 match decode_args(&bytes) {
555 Err(e) => trap(&format!("failed to decode call arguments: {:?}", e)),
556 Ok(r) => r,
557 }
558}
559
560/// Accepts the ingress message.
561pub fn accept_message() {
562 // SAFETY: ic0.accept_message is always safe to call.
563 unsafe {
564 ic0::accept_message();
565 }
566}
567
568/// Returns the name of current canister method.
569pub fn method_name() -> String {
570 // SAFETY: ic0.msg_method_name_size is always safe to call.
571 let len: u32 = unsafe { ic0::msg_method_name_size() as u32 };
572 let mut bytes = vec![0u8; len as usize];
573 // SAFETY: `bytes` is writable and allocated to `len` bytes, and therefore can be safely passed to ic0.msg_method_name_copy
574 unsafe {
575 ic0::msg_method_name_copy(bytes.as_mut_ptr() as i32, 0, len as i32);
576 }
577 String::from_utf8_lossy(&bytes).into_owned()
578}
579
580/// Get the value of specified performance counter
581///
582/// Supported counter type:
583/// 0 : instruction counter. The number of WebAssembly instructions the system has determined that the canister has executed.
584pub fn performance_counter(counter_type: u32) -> u64 {
585 // SAFETY: ic0.performance_counter is always safe to call.
586 unsafe { ic0::performance_counter(counter_type as i32) as u64 }
587}
588
589/// Pretends to have the Candid type `T`, but unconditionally errors
590/// when serialized.
591///
592/// Usable, but not required, as metadata when using `#[query(manual_reply = true)]`,
593/// so an accurate Candid file can still be generated.
594#[derive(Debug, Copy, Clone, Default)]
595pub struct ManualReply<T: ?Sized>(PhantomData<T>);
596
597impl<T: ?Sized> ManualReply<T> {
598 /// Constructs a new `ManualReply`.
599 #[allow(clippy::self_named_constructors)]
600 pub const fn empty() -> Self {
601 Self(PhantomData)
602 }
603 /// Replies with the given value and returns a new `ManualReply`,
604 /// for a useful reply-then-return shortcut.
605 pub fn all<U>(value: U) -> Self
606 where
607 U: ArgumentEncoder,
608 {
609 reply(value);
610 Self::empty()
611 }
612 /// Replies with a one-element tuple around the given value and returns
613 /// a new `ManualReply`, for a useful reply-then-return shortcut.
614 pub fn one<U>(value: U) -> Self
615 where
616 U: CandidType,
617 {
618 reply((value,));
619 Self::empty()
620 }
621
622 /// Rejects the call with the specified message and returns a new
623 /// `ManualReply`, for a useful reply-then-return shortcut.
624 pub fn reject(message: impl AsRef<str>) -> Self {
625 reject(message.as_ref());
626 Self::empty()
627 }
628}
629
630impl<T> CandidType for ManualReply<T>
631where
632 T: CandidType + ?Sized,
633{
634 fn _ty() -> candid::types::Type {
635 T::_ty()
636 }
637 /// Unconditionally errors.
638 fn idl_serialize<S>(&self, _: S) -> Result<(), S::Error>
639 where
640 S: candid::types::Serializer,
641 {
642 Err(S::Error::custom("`Empty` cannot be serialized"))
643 }
644}