canic_core/ops/request/
request.rs

1use crate::{
2    Error,
3    cdk::call::Call,
4    ids::CanisterRole,
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///
17/// Request
18/// Root-directed orchestration commands.
19///
20
21#[derive(CandidType, Clone, Debug, Deserialize)]
22pub enum Request {
23    CreateCanister(CreateCanisterRequest),
24    UpgradeCanister(UpgradeCanisterRequest),
25    Cycles(CyclesRequest),
26}
27
28///
29/// CreateCanisterRequest
30/// Payload for [`Request::CreateCanister`]
31///
32
33#[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///
41/// CreateCanisterParent
42/// Parent-location choices for a new canister
43///
44
45#[derive(CandidType, Clone, Debug, Deserialize)]
46pub enum CreateCanisterParent {
47    Root,
48    /// Use the requesting canister as parent.
49    ThisCanister,
50    /// Use the requesting canister's parent (creates a sibling).
51    Parent,
52    Canister(Principal),
53    Directory(CanisterRole),
54}
55
56///
57/// UpgradeCanisterRequest
58/// Payload for [`Request::UpgradeCanister`]
59///
60
61#[derive(CandidType, Clone, Debug, Deserialize)]
62pub struct UpgradeCanisterRequest {
63    pub canister_pid: Principal,
64    pub canister_type: CanisterRole,
65}
66
67///
68/// CyclesRequest
69/// Payload for [`Request::Cycles`]
70///
71
72#[derive(CandidType, Clone, Debug, Deserialize)]
73pub struct CyclesRequest {
74    pub cycles: u128,
75}
76
77/// Send a request to the root canister and decode its response.
78async 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
88/// Ask root to create and install a canister of the given type.
89pub 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
99    // build request
100    let q = Request::CreateCanister(CreateCanisterRequest {
101        canister_role: canister_role.clone(),
102        parent,
103        extra_arg: encoded,
104    });
105
106    match request(q).await? {
107        Response::CreateCanister(res) => Ok(res),
108        _ => Err(RequestOpsError::InvalidResponseType.into()),
109    }
110}
111
112/// Ask root to upgrade a child canister to its latest registered WASM.
113pub async fn upgrade_canister_request(
114    canister_pid: Principal,
115) -> Result<UpgradeCanisterResponse, Error> {
116    // check this is a valid child
117    let canister = SubnetCanisterChildrenOps::find_by_pid(&canister_pid)
118        .ok_or(RequestOpsError::ChildNotFound(canister_pid))?;
119
120    // send the request
121    let q = Request::UpgradeCanister(UpgradeCanisterRequest {
122        canister_pid: canister.pid,
123        canister_type: canister.ty,
124    });
125
126    match request(q).await? {
127        Response::UpgradeCanister(res) => Ok(res),
128        _ => Err(RequestOpsError::InvalidResponseType.into()),
129    }
130}
131
132/// Request a cycle transfer from root to the current canister.
133pub async fn cycles_request(cycles: u128) -> Result<CyclesResponse, Error> {
134    OpsError::deny_root()?;
135
136    let q = Request::Cycles(CyclesRequest { cycles });
137
138    match request(q).await? {
139        Response::Cycles(res) => Ok(res),
140        _ => Err(RequestOpsError::InvalidResponseType.into()),
141    }
142}