junobuild_shared/ic/call.rs
1use candid::{encode_one, CandidType};
2use ic_cdk::api::{msg_reject, msg_reply};
3use ic_cdk::trap;
4use std::marker::PhantomData;
5
6/// Helper for replying or rejecting calls marked with `manual_reply = true`.
7///
8/// This struct mirrors the now-deprecated `ManualReply<T>` type from the
9/// IC Rust CDK. It uses `PhantomData<T>` internally to preserve the correct
10/// return type in the generated Candid interface, but never actually serializes
11/// anything because you send the reply or reject manually.
12///
13/// # When to use
14///
15/// - Your method is annotated with `#[update(..., manual_reply = true)]`
16/// or `#[query(..., manual_reply = true)]`.
17/// - You want to send the reply or reject explicitly, rather than having
18/// the CDK serialize and return your function's result automatically.
19/// - You still want the generated `.did` file to correctly show the return type.
20///
21/// # Example
22///
23/// ```rust
24/// #[update(manual_reply = true)]
25/// fn my_method() -> ManualReply<()> {
26/// if something_went_wrong() {
27/// return ManualReply::reject("bad input");
28/// }
29/// ManualReply::one(())
30/// }
31/// ```
32#[derive(Debug, Copy, Clone, Default)]
33pub struct ManualReply<T: ?Sized>(PhantomData<T>);
34
35impl<T: ?Sized> ManualReply<T> {
36 /// Returns a `ManualReply<T>` without sending a reply or reject.
37 ///
38 /// Only useful once reply/reject were sent manually earlier.
39 const fn done() -> Self {
40 Self(PhantomData)
41 }
42
43 /// Sends a reply containing the given value as a single Candid return.
44 ///
45 /// # Example
46 /// ```
47 /// ManualReply::one(42u64); // replies with 42
48 /// ```
49 pub fn one<U>(value: U) -> Self
50 where
51 U: CandidType,
52 {
53 let bytes =
54 encode_one(value).unwrap_or_else(|e| trap(format!("Candid encode failed: {e}")));
55 msg_reply(bytes);
56 Self::done()
57 }
58
59 /// Rejects the call with the given message (user-level reject).
60 ///
61 /// # Example
62 /// ```
63 /// ManualReply::reject("Not allowed");
64 /// ```
65 pub fn reject(message: impl AsRef<str>) -> Self {
66 msg_reject(message.as_ref());
67 Self::done()
68 }
69}
70
71impl<T> CandidType for ManualReply<T>
72where
73 T: CandidType + ?Sized,
74{
75 fn _ty() -> candid::types::Type {
76 T::_ty()
77 }
78
79 fn idl_serialize<S>(&self, _: S) -> Result<(), S::Error>
80 where
81 S: candid::types::Serializer,
82 {
83 Err(<S::Error as serde::ser::Error>::custom(
84 "ManualReply cannot be serialized (manual_reply = true)",
85 ))
86 }
87}