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)]
318mod tests {
319 use super::*;
320
321 fn p(id: u8) -> Principal {
322 Principal::from_slice(&[id; 29])
323 }
324
325 fn metadata(id: u8) -> RootRequestMetadata {
326 RootRequestMetadata {
327 request_id: [id; 32],
328 ttl_ns: 60_000_000_000,
329 }
330 }
331
332 fn requests_with_no_metadata() -> Vec<Request> {
333 vec![
334 Request::create_canister(CreateCanisterRequest {
335 canister_role: CanisterRole::new("app"),
336 parent: CreateCanisterParent::Root,
337 extra_arg: None,
338 metadata: None,
339 }),
340 Request::upgrade_canister(UpgradeCanisterRequest {
341 canister_pid: p(2),
342 metadata: None,
343 }),
344 Request::recycle_canister(RecycleCanisterRequest {
345 canister_pid: p(7),
346 metadata: None,
347 }),
348 Request::cycles(CyclesRequest {
349 cycles: 100,
350 metadata: None,
351 }),
352 ]
353 }
354
355 #[test]
356 fn request_family_matches_all_variants() {
357 let families: Vec<RequestFamily> = requests_with_no_metadata()
358 .iter()
359 .map(Request::family)
360 .collect();
361 assert_eq!(
362 families,
363 vec![
364 RequestFamily::Provision,
365 RequestFamily::Upgrade,
366 RequestFamily::RecycleCanister,
367 RequestFamily::RequestCycles,
368 ]
369 );
370 }
371
372 #[test]
373 fn with_metadata_and_without_metadata_cover_all_variants() {
374 let replay_meta = metadata(7);
375
376 for request in requests_with_no_metadata() {
377 let with_meta = request.clone().with_metadata(replay_meta);
378 assert_eq!(
379 with_meta.metadata(),
380 Some(replay_meta),
381 "with_metadata must set metadata for every request variant"
382 );
383
384 let without_meta = with_meta.without_metadata();
385 assert_eq!(
386 without_meta.metadata(),
387 None,
388 "without_metadata must strip metadata for every request variant"
389 );
390 }
391 }
392
393 #[test]
394 fn upgrade_request_is_only_available_for_upgrade_variant() {
395 let upgrade = Request::upgrade_canister(UpgradeCanisterRequest {
396 canister_pid: p(9),
397 metadata: Some(metadata(9)),
398 });
399 assert!(upgrade.upgrade_request().is_some());
400
401 for request in requests_with_no_metadata() {
402 if !matches!(request, Request::UpgradeCanister(_)) {
403 assert!(request.upgrade_request().is_none());
404 }
405 }
406 }
407}