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}