canic_core/ops/rpc/
request.rs

1use crate::{
2    Error, ThisError,
3    ids::CanisterRole,
4    ops::{
5        prelude::*,
6        rpc::{
7            CreateCanisterResponse, CyclesResponse, Response, Rpc, RpcOpsError,
8            UpgradeCanisterResponse, execute_rpc,
9        },
10    },
11};
12use candid::encode_one;
13
14///
15/// RequestOpsError
16/// Errors produced during request dispatch or response handling
17///
18
19#[derive(Debug, ThisError)]
20pub enum RequestOpsError {
21    #[error("canister role {0} not found")]
22    CanisterRoleNotFound(CanisterRole),
23
24    #[error("child canister {0} not found")]
25    ChildNotFound(Principal),
26
27    #[error("canister {0} is not a child of caller {1}")]
28    NotChildOfCaller(Principal, Principal),
29
30    #[error("canister {0}'s parent was not found")]
31    ParentNotFound(Principal),
32
33    #[error("invalid response type")]
34    InvalidResponseType,
35
36    #[error("create_canister: missing new pid")]
37    MissingNewCanisterPid,
38}
39
40impl From<RequestOpsError> for Error {
41    fn from(err: RequestOpsError) -> Self {
42        RpcOpsError::from(err).into()
43    }
44}
45
46///
47/// Request
48/// Root-directed orchestration commands.
49///
50
51#[derive(CandidType, Clone, Debug, Deserialize)]
52pub enum Request {
53    CreateCanister(CreateCanisterRequest),
54    UpgradeCanister(UpgradeCanisterRequest),
55    Cycles(CyclesRequest),
56}
57
58///
59/// CreateCanisterRequest
60/// Payload for [`Request::CreateCanister`]
61///
62
63#[derive(CandidType, Clone, Debug, Deserialize)]
64pub struct CreateCanisterRequest {
65    pub canister_role: CanisterRole,
66    pub parent: CreateCanisterParent,
67    pub extra_arg: Option<Vec<u8>>,
68}
69
70///
71/// CreateCanisterParent
72/// Parent-location choices for a new canister
73///
74
75#[derive(CandidType, Clone, Debug, Deserialize)]
76pub enum CreateCanisterParent {
77    Root,
78    /// Use the requesting canister as parent.
79    ThisCanister,
80    /// Use the requesting canister's parent (creates a sibling).
81    Parent,
82    Canister(Principal),
83    Directory(CanisterRole),
84}
85
86///
87/// UpgradeCanisterRequest
88/// Payload for [`Request::UpgradeCanister`]
89///
90
91#[derive(CandidType, Clone, Debug, Deserialize)]
92pub struct UpgradeCanisterRequest {
93    pub canister_pid: Principal,
94}
95
96///
97/// CyclesRequest
98/// Payload for [`Request::Cycles`]
99///
100
101#[derive(CandidType, Clone, Debug, Deserialize)]
102pub struct CyclesRequest {
103    pub cycles: u128,
104}
105
106///
107/// CreateCanister
108///
109
110pub async fn create_canister_request<A>(
111    canister_role: &CanisterRole,
112    parent: CreateCanisterParent,
113    extra: Option<A>,
114) -> Result<CreateCanisterResponse, Error>
115where
116    A: CandidType + Send + Sync,
117{
118    let extra_arg = extra.map(encode_one).transpose()?;
119
120    execute_rpc(CreateCanisterRpc {
121        canister_role: canister_role.clone(),
122        parent,
123        extra_arg,
124    })
125    .await
126}
127
128pub struct CreateCanisterRpc {
129    pub canister_role: CanisterRole,
130    pub parent: CreateCanisterParent,
131    pub extra_arg: Option<Vec<u8>>,
132}
133
134impl Rpc for CreateCanisterRpc {
135    type Response = CreateCanisterResponse;
136
137    fn into_request(self) -> Request {
138        Request::CreateCanister(CreateCanisterRequest {
139            canister_role: self.canister_role,
140            parent: self.parent,
141            extra_arg: self.extra_arg,
142        })
143    }
144
145    fn try_from_response(resp: Response) -> Result<Self::Response, RequestOpsError> {
146        match resp {
147            Response::CreateCanister(r) => Ok(r),
148            _ => Err(RequestOpsError::InvalidResponseType),
149        }
150    }
151}
152
153///
154/// UpgradeCanister
155/// Ask root to upgrade a child canister to its latest registered WASM.
156///
157
158pub async fn upgrade_canister_request(
159    canister_pid: Principal,
160) -> Result<UpgradeCanisterResponse, Error> {
161    execute_rpc(UpgradeCanisterRpc { canister_pid }).await
162}
163
164pub struct UpgradeCanisterRpc {
165    pub canister_pid: Principal,
166}
167
168impl Rpc for UpgradeCanisterRpc {
169    type Response = UpgradeCanisterResponse;
170
171    fn into_request(self) -> Request {
172        Request::UpgradeCanister(UpgradeCanisterRequest {
173            canister_pid: self.canister_pid,
174        })
175    }
176
177    fn try_from_response(resp: Response) -> Result<Self::Response, RequestOpsError> {
178        match resp {
179            Response::UpgradeCanister(r) => Ok(r),
180            _ => Err(RequestOpsError::InvalidResponseType),
181        }
182    }
183}
184
185///
186/// Cycles
187/// Request a cycle transfer from root to the current canister.
188///
189
190pub async fn cycles_request(cycles: u128) -> Result<CyclesResponse, Error> {
191    OpsError::deny_root()?;
192
193    execute_rpc(CyclesRpc { cycles }).await
194}
195
196pub struct CyclesRpc {
197    pub cycles: u128,
198}
199
200impl Rpc for CyclesRpc {
201    type Response = CyclesResponse;
202
203    fn into_request(self) -> Request {
204        Request::Cycles(CyclesRequest {
205            cycles: self.cycles,
206        })
207    }
208
209    fn try_from_response(resp: Response) -> Result<Self::Response, RequestOpsError> {
210        match resp {
211            Response::Cycles(r) => Ok(r),
212            _ => Err(RequestOpsError::InvalidResponseType),
213        }
214    }
215}