1use crate::dto::prelude::*;
2
3#[derive(CandidType, Clone, Debug, Deserialize)]
10pub enum Request {
11 CreateCanister(CreateCanisterRequest),
12 UpgradeCanister(UpgradeCanisterRequest),
13 RecycleCanister(RecycleCanisterRequest),
14 Cycles(CyclesRequest),
15}
16
17impl Request {
18 #[must_use]
22 pub const fn create_canister(request: CreateCanisterRequest) -> Self {
23 Self::CreateCanister(request)
24 }
25
26 #[must_use]
30 pub const fn upgrade_canister(request: UpgradeCanisterRequest) -> Self {
31 Self::UpgradeCanister(request)
32 }
33
34 #[must_use]
38 pub const fn recycle_canister(request: RecycleCanisterRequest) -> Self {
39 Self::RecycleCanister(request)
40 }
41
42 #[must_use]
46 pub const fn cycles(request: CyclesRequest) -> Self {
47 Self::Cycles(request)
48 }
49
50 #[must_use]
54 pub const fn family(&self) -> RequestFamily {
55 match self {
56 Self::CreateCanister(_) => RequestFamily::Provision,
57 Self::UpgradeCanister(_) => RequestFamily::Upgrade,
58 Self::RecycleCanister(_) => RequestFamily::RecycleCanister,
59 Self::Cycles(_) => RequestFamily::RequestCycles,
60 }
61 }
62
63 #[must_use]
67 pub const fn metadata(&self) -> Option<RootRequestMetadata> {
68 match self {
69 Self::CreateCanister(req) => req.metadata,
70 Self::UpgradeCanister(req) => req.metadata,
71 Self::RecycleCanister(req) => req.metadata,
72 Self::Cycles(req) => req.metadata,
73 }
74 }
75
76 #[must_use]
80 pub const fn with_metadata(mut self, metadata: RootRequestMetadata) -> Self {
81 match &mut self {
82 Self::CreateCanister(req) => req.metadata = Some(metadata),
83 Self::UpgradeCanister(req) => req.metadata = Some(metadata),
84 Self::RecycleCanister(req) => req.metadata = Some(metadata),
85 Self::Cycles(req) => req.metadata = Some(metadata),
86 }
87 self
88 }
89
90 #[must_use]
94 pub const fn without_metadata(mut self) -> Self {
95 match &mut self {
96 Self::CreateCanister(req) => req.metadata = None,
97 Self::UpgradeCanister(req) => req.metadata = None,
98 Self::RecycleCanister(req) => req.metadata = None,
99 Self::Cycles(req) => req.metadata = None,
100 }
101 self
102 }
103
104 #[must_use]
109 pub const fn canonical_capability_payload(mut self) -> Self {
110 self = self.without_metadata();
111 self
112 }
113
114 #[must_use]
118 pub const fn upgrade_request(&self) -> Option<&UpgradeCanisterRequest> {
119 match self {
120 Self::UpgradeCanister(request) => Some(request),
121 _ => None,
122 }
123 }
124}
125
126#[derive(Clone, Copy, Debug, Eq, PartialEq)]
133pub enum RequestFamily {
134 Provision,
135 Upgrade,
136 RecycleCanister,
137 RequestCycles,
138}
139
140impl RequestFamily {
141 #[must_use]
145 pub const fn label(self) -> &'static str {
146 match self {
147 Self::Provision => "Provision",
148 Self::Upgrade => "Upgrade",
149 Self::RecycleCanister => "RecycleCanister",
150 Self::RequestCycles => "RequestCycles",
151 }
152 }
153}
154
155#[derive(CandidType, Clone, Debug, Deserialize)]
162pub enum RootCapabilityCommand {
163 ProvisionCanister(CreateCanisterRequest),
164 UpgradeCanister(UpgradeCanisterRequest),
165 RecycleCanister(RecycleCanisterRequest),
166 RequestCycles(CyclesRequest),
167}
168
169impl From<Request> for RootCapabilityCommand {
170 fn from(value: Request) -> Self {
171 match value {
172 Request::CreateCanister(req) => Self::ProvisionCanister(req),
173 Request::UpgradeCanister(req) => Self::UpgradeCanister(req),
174 Request::RecycleCanister(req) => Self::RecycleCanister(req),
175 Request::Cycles(req) => Self::RequestCycles(req),
176 }
177 }
178}
179
180#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
187pub struct RootRequestMetadata {
188 pub request_id: [u8; 32],
189 pub ttl_ns: u64,
190}
191
192#[derive(CandidType, Clone, Debug, Deserialize)]
199pub struct CreateCanisterRequest {
200 pub canister_role: CanisterRole,
201 pub parent: CreateCanisterParent,
202 pub extra_arg: Option<Vec<u8>>,
203 #[serde(default)]
204 pub metadata: Option<RootRequestMetadata>,
205}
206
207#[derive(CandidType, Clone, Debug, Deserialize)]
214pub enum CreateCanisterParent {
215 Root,
216 ThisCanister,
218 Parent,
220 Canister(Principal),
221 Index(CanisterRole),
222}
223
224#[derive(CandidType, Clone, Debug, Deserialize)]
231pub struct UpgradeCanisterRequest {
232 pub canister_pid: Principal,
233 #[serde(default)]
234 pub metadata: Option<RootRequestMetadata>,
235}
236
237#[derive(CandidType, Clone, Debug, Deserialize)]
244pub struct RecycleCanisterRequest {
245 pub canister_pid: Principal,
246 #[serde(default)]
247 pub metadata: Option<RootRequestMetadata>,
248}
249
250#[derive(CandidType, Clone, Debug, Deserialize)]
257pub struct CyclesRequest {
258 pub cycles: u128,
259 #[serde(default)]
260 pub metadata: Option<RootRequestMetadata>,
261}
262
263#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
270pub enum Response {
271 CreateCanister(CreateCanisterResponse),
272 UpgradeCanister(UpgradeCanisterResponse),
273 RecycleCanister(RecycleCanisterResponse),
274 Cycles(CyclesResponse),
275}
276
277#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
283pub struct CreateCanisterResponse {
284 pub new_canister_pid: Principal,
285}
286
287#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
293pub struct UpgradeCanisterResponse {}
294
295#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
301pub struct RecycleCanisterResponse {}
302
303#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
309pub struct CyclesResponse {
310 pub cycles_transferred: u128,
311}
312
313#[cfg(test)]
316mod tests {
317 use super::*;
318
319 fn p(id: u8) -> Principal {
320 Principal::from_slice(&[id; 29])
321 }
322
323 fn metadata(id: u8) -> RootRequestMetadata {
324 RootRequestMetadata {
325 request_id: [id; 32],
326 ttl_ns: 60_000_000_000,
327 }
328 }
329
330 fn requests_with_no_metadata() -> Vec<Request> {
331 vec![
332 Request::create_canister(CreateCanisterRequest {
333 canister_role: CanisterRole::new("app"),
334 parent: CreateCanisterParent::Root,
335 extra_arg: None,
336 metadata: None,
337 }),
338 Request::upgrade_canister(UpgradeCanisterRequest {
339 canister_pid: p(2),
340 metadata: None,
341 }),
342 Request::recycle_canister(RecycleCanisterRequest {
343 canister_pid: p(7),
344 metadata: None,
345 }),
346 Request::cycles(CyclesRequest {
347 cycles: 100,
348 metadata: None,
349 }),
350 ]
351 }
352
353 #[test]
354 fn request_family_matches_all_variants() {
355 let families: Vec<RequestFamily> = requests_with_no_metadata()
356 .iter()
357 .map(Request::family)
358 .collect();
359 assert_eq!(
360 families,
361 vec![
362 RequestFamily::Provision,
363 RequestFamily::Upgrade,
364 RequestFamily::RecycleCanister,
365 RequestFamily::RequestCycles,
366 ]
367 );
368 }
369
370 #[test]
371 fn with_metadata_and_without_metadata_cover_all_variants() {
372 let replay_meta = metadata(7);
373
374 for request in requests_with_no_metadata() {
375 let with_meta = request.clone().with_metadata(replay_meta);
376 assert_eq!(
377 with_meta.metadata(),
378 Some(replay_meta),
379 "with_metadata must set metadata for every request variant"
380 );
381
382 let without_meta = with_meta.without_metadata();
383 assert_eq!(
384 without_meta.metadata(),
385 None,
386 "without_metadata must strip metadata for every request variant"
387 );
388 }
389 }
390
391 #[test]
392 fn upgrade_request_is_only_available_for_upgrade_variant() {
393 let upgrade = Request::upgrade_canister(UpgradeCanisterRequest {
394 canister_pid: p(9),
395 metadata: Some(metadata(9)),
396 });
397 assert!(upgrade.upgrade_request().is_some());
398
399 for request in requests_with_no_metadata() {
400 if !matches!(request, Request::UpgradeCanister(_)) {
401 assert!(request.upgrade_request().is_none());
402 }
403 }
404 }
405}