canic_core/ops/request/
request.rs1use crate::{
2 Error,
3 ids::CanisterRole,
4 log::Topic,
5 ops::{
6 model::memory::{EnvOps, topology::SubnetCanisterChildrenOps},
7 prelude::*,
8 request::{
9 CreateCanisterResponse, CyclesResponse, RequestOpsError, Response,
10 UpgradeCanisterResponse,
11 },
12 },
13};
14use candid::encode_one;
15
16#[derive(CandidType, Clone, Debug, Deserialize)]
22pub enum Request {
23 CreateCanister(CreateCanisterRequest),
24 UpgradeCanister(UpgradeCanisterRequest),
25 Cycles(CyclesRequest),
26}
27
28#[derive(CandidType, Clone, Debug, Deserialize)]
34pub struct CreateCanisterRequest {
35 pub canister_role: CanisterRole,
36 pub parent: CreateCanisterParent,
37 pub extra_arg: Option<Vec<u8>>,
38}
39
40#[derive(CandidType, Clone, Debug, Deserialize)]
46pub enum CreateCanisterParent {
47 Root,
48 ThisCanister,
50 Parent,
52 Canister(Principal),
53 Directory(CanisterRole),
54}
55
56#[derive(CandidType, Clone, Debug, Deserialize)]
62pub struct UpgradeCanisterRequest {
63 pub canister_pid: Principal,
64 pub canister_type: CanisterRole,
65}
66
67#[derive(CandidType, Clone, Debug, Deserialize)]
73pub struct CyclesRequest {
74 pub cycles: u128,
75}
76
77async fn request(request: Request) -> Result<Response, Error> {
79 let root_pid = EnvOps::try_get_root_pid().map_err(|_| RequestOpsError::RootNotFound)?;
80
81 let call_response = Call::unbounded_wait(root_pid, "canic_response")
82 .with_arg(&request)
83 .await?;
84
85 call_response.candid::<Result<Response, Error>>()?
86}
87
88pub async fn create_canister_request<A>(
90 canister_role: &CanisterRole,
91 parent: CreateCanisterParent,
92 extra: Option<A>,
93) -> Result<CreateCanisterResponse, Error>
94where
95 A: CandidType + Send + Sync,
96{
97 let encoded = extra.map(|v| encode_one(v)).transpose()?;
98 let role = canister_role.clone();
99 let parent_desc = format!("{:?}", &parent);
100 let caller_ty =
101 EnvOps::try_get_canister_type().map_or_else(|_| "unknown".to_string(), |ty| ty.to_string());
102
103 let q = Request::CreateCanister(CreateCanisterRequest {
105 canister_role: canister_role.clone(),
106 parent,
107 extra_arg: encoded,
108 });
109
110 match request(q).await {
111 Ok(Response::CreateCanister(res)) => Ok(res),
112 Ok(_) => {
113 log!(
114 Topic::CanisterLifecycle,
115 Warn,
116 "create_canister_request: invalid response type (caller={caller_ty}, role={role}, parent={parent_desc})"
117 );
118
119 Err(RequestOpsError::InvalidResponseType.into())
120 }
121 Err(err) => {
122 log!(
123 Topic::CanisterLifecycle,
124 Warn,
125 "create_canister_request failed (caller={caller_ty}, role={role}, parent={parent_desc}): {err}"
126 );
127
128 Err(err)
129 }
130 }
131}
132
133pub async fn upgrade_canister_request(
135 canister_pid: Principal,
136) -> Result<UpgradeCanisterResponse, Error> {
137 let canister = SubnetCanisterChildrenOps::find_by_pid(&canister_pid)
139 .ok_or(RequestOpsError::ChildNotFound(canister_pid))?;
140
141 let q = Request::UpgradeCanister(UpgradeCanisterRequest {
143 canister_pid: canister.pid,
144 canister_type: canister.ty,
145 });
146
147 match request(q).await? {
148 Response::UpgradeCanister(res) => Ok(res),
149 _ => Err(RequestOpsError::InvalidResponseType.into()),
150 }
151}
152
153pub async fn cycles_request(cycles: u128) -> Result<CyclesResponse, Error> {
155 OpsError::deny_root()?;
156
157 let q = Request::Cycles(CyclesRequest { cycles });
158
159 match request(q).await? {
160 Response::Cycles(res) => Ok(res),
161 _ => Err(RequestOpsError::InvalidResponseType.into()),
162 }
163}