Skip to main content

canic_core/api/ic/
call.rs

1//! Public IC call façade.
2//!
3//! This module defines the stable, public API used by application code to make
4//! inter-canister calls. It deliberately exposes a *thin* surface:
5//!
6//! - argument encoding
7//! - cycle attachment
8//!
9//! It does NOT:
10//! - perform orchestration itself
11//! - leak workflow or storage details
12//!
13//! This separation keeps application code simple while ensuring correctness
14//! under concurrency.
15use crate::{
16    cdk::{candid::CandidType, types::Principal},
17    dto::error::Error,
18    workflow::ic::call::{
19        CallBuilder as WorkflowCallBuilder, CallResult as WorkflowCallResult, CallWorkflow,
20    },
21};
22use candid::utils::{ArgumentDecoder, ArgumentEncoder};
23use serde::de::DeserializeOwned;
24use std::borrow::Cow;
25
26///
27/// Call
28///
29/// Entry point for constructing inter-canister calls.
30///
31/// `Call` itself has no state; it simply selects the wait semantics
32/// (bounded vs unbounded) and produces a `CallBuilder`.
33///
34/// Think of this as the *verb* (“make a call”), not the call itself.
35///
36
37pub struct Call;
38
39impl Call {
40    #[must_use]
41    pub fn bounded_wait(canister_id: impl Into<Principal>, method: &str) -> CallBuilder<'static> {
42        CallBuilder {
43            inner: CallWorkflow::bounded_wait(canister_id, method),
44        }
45    }
46
47    #[must_use]
48    pub fn unbounded_wait(canister_id: impl Into<Principal>, method: &str) -> CallBuilder<'static> {
49        CallBuilder {
50            inner: CallWorkflow::unbounded_wait(canister_id, method),
51        }
52    }
53}
54
55///
56/// CallBuilder (api)
57///
58
59pub struct CallBuilder<'a> {
60    inner: WorkflowCallBuilder<'a>,
61}
62
63impl CallBuilder<'_> {
64    // ---------- arguments ----------
65
66    /// Encode a single argument into Candid bytes (fallible).
67    pub fn with_arg<A>(self, arg: A) -> Result<Self, Error>
68    where
69        A: CandidType,
70    {
71        Ok(Self {
72            inner: self.inner.with_arg(arg).map_err(Error::from)?,
73        })
74    }
75
76    /// Encode multiple arguments into Candid bytes (fallible).
77    pub fn with_args<A>(self, args: A) -> Result<Self, Error>
78    where
79        A: ArgumentEncoder,
80    {
81        Ok(Self {
82            inner: self.inner.with_args(args).map_err(Error::from)?,
83        })
84    }
85
86    /// Use pre-encoded Candid arguments (no validation performed).
87    #[must_use]
88    pub fn with_raw_args<'b>(self, args: impl Into<Cow<'b, [u8]>>) -> CallBuilder<'b> {
89        CallBuilder {
90            inner: self.inner.with_raw_args(args),
91        }
92    }
93
94    // ---------- cycles ----------
95
96    #[must_use]
97    pub fn with_cycles(self, cycles: u128) -> Self {
98        Self {
99            inner: self.inner.with_cycles(cycles),
100        }
101    }
102
103    // ---------- execution ----------
104
105    pub async fn execute(self) -> Result<CallResult, Error> {
106        Ok(CallResult {
107            inner: self.inner.execute().await.map_err(Error::from)?,
108        })
109    }
110}
111
112///
113/// CallResult
114///
115/// Stable wrapper around an inter-canister call response.
116///
117/// This type exists to:
118/// - decouple API consumers from infra response types
119/// - provide uniform decoding helpers
120/// - allow future extension without breaking callers
121///
122
123pub struct CallResult {
124    inner: WorkflowCallResult,
125}
126
127impl CallResult {
128    pub fn candid<R>(&self) -> Result<R, Error>
129    where
130        R: CandidType + DeserializeOwned,
131    {
132        self.inner.candid().map_err(Error::from)
133    }
134
135    pub fn candid_tuple<R>(&self) -> Result<R, Error>
136    where
137        R: for<'de> ArgumentDecoder<'de>,
138    {
139        self.inner.candid_tuple().map_err(Error::from)
140    }
141}