1use crate::dto::{
2 auth::{
3 DelegationProvisionResponse, DelegationRequest, RoleAttestationRequest,
4 SignedRoleAttestation,
5 },
6 prelude::*,
7};
8
9#[derive(CandidType, Clone, Debug, Deserialize)]
16pub enum Request {
17 CreateCanister(CreateCanisterRequest),
18 UpgradeCanister(UpgradeCanisterRequest),
19 RecycleCanister(RecycleCanisterRequest),
20 Cycles(CyclesRequest),
21 IssueDelegation(DelegationRequest),
22 IssueRoleAttestation(RoleAttestationRequest),
23}
24
25impl Request {
26 #[must_use]
30 pub const fn create_canister(request: CreateCanisterRequest) -> Self {
31 Self::CreateCanister(request)
32 }
33
34 #[must_use]
38 pub const fn upgrade_canister(request: UpgradeCanisterRequest) -> Self {
39 Self::UpgradeCanister(request)
40 }
41
42 #[must_use]
46 pub const fn recycle_canister(request: RecycleCanisterRequest) -> Self {
47 Self::RecycleCanister(request)
48 }
49
50 #[must_use]
54 pub const fn cycles(request: CyclesRequest) -> Self {
55 Self::Cycles(request)
56 }
57
58 #[must_use]
62 pub const fn issue_delegation(request: DelegationRequest) -> Self {
63 Self::IssueDelegation(request)
64 }
65
66 #[must_use]
70 pub const fn issue_role_attestation(request: RoleAttestationRequest) -> Self {
71 Self::IssueRoleAttestation(request)
72 }
73
74 #[must_use]
78 pub const fn family(&self) -> RequestFamily {
79 match self {
80 Self::CreateCanister(_) => RequestFamily::Provision,
81 Self::UpgradeCanister(_) => RequestFamily::Upgrade,
82 Self::RecycleCanister(_) => RequestFamily::RecycleCanister,
83 Self::Cycles(_) => RequestFamily::RequestCycles,
84 Self::IssueDelegation(_) => RequestFamily::IssueDelegation,
85 Self::IssueRoleAttestation(_) => RequestFamily::IssueRoleAttestation,
86 }
87 }
88
89 #[must_use]
93 pub const fn metadata(&self) -> Option<RootRequestMetadata> {
94 match self {
95 Self::CreateCanister(req) => req.metadata,
96 Self::UpgradeCanister(req) => req.metadata,
97 Self::RecycleCanister(req) => req.metadata,
98 Self::Cycles(req) => req.metadata,
99 Self::IssueDelegation(req) => req.metadata,
100 Self::IssueRoleAttestation(req) => req.metadata,
101 }
102 }
103
104 #[must_use]
108 pub const fn with_metadata(mut self, metadata: RootRequestMetadata) -> Self {
109 match &mut self {
110 Self::CreateCanister(req) => req.metadata = Some(metadata),
111 Self::UpgradeCanister(req) => req.metadata = Some(metadata),
112 Self::RecycleCanister(req) => req.metadata = Some(metadata),
113 Self::Cycles(req) => req.metadata = Some(metadata),
114 Self::IssueDelegation(req) => req.metadata = Some(metadata),
115 Self::IssueRoleAttestation(req) => req.metadata = Some(metadata),
116 }
117 self
118 }
119
120 #[must_use]
124 pub const fn without_metadata(mut self) -> Self {
125 match &mut self {
126 Self::CreateCanister(req) => req.metadata = None,
127 Self::UpgradeCanister(req) => req.metadata = None,
128 Self::RecycleCanister(req) => req.metadata = None,
129 Self::Cycles(req) => req.metadata = None,
130 Self::IssueDelegation(req) => req.metadata = None,
131 Self::IssueRoleAttestation(req) => req.metadata = None,
132 }
133 self
134 }
135
136 #[must_use]
140 pub const fn upgrade_request(&self) -> Option<&UpgradeCanisterRequest> {
141 match self {
142 Self::UpgradeCanister(request) => Some(request),
143 _ => None,
144 }
145 }
146}
147
148#[derive(Clone, Copy, Debug, Eq, PartialEq)]
155pub enum RequestFamily {
156 Provision,
157 Upgrade,
158 RecycleCanister,
159 RequestCycles,
160 IssueDelegation,
161 IssueRoleAttestation,
162}
163
164impl RequestFamily {
165 #[must_use]
169 pub const fn label(self) -> &'static str {
170 match self {
171 Self::Provision => "Provision",
172 Self::Upgrade => "Upgrade",
173 Self::RecycleCanister => "RecycleCanister",
174 Self::RequestCycles => "RequestCycles",
175 Self::IssueDelegation => "IssueDelegation",
176 Self::IssueRoleAttestation => "IssueRoleAttestation",
177 }
178 }
179}
180
181#[derive(CandidType, Clone, Debug, Deserialize)]
188pub enum RootCapabilityCommand {
189 ProvisionCanister(CreateCanisterRequest),
190 UpgradeCanister(UpgradeCanisterRequest),
191 RecycleCanister(RecycleCanisterRequest),
192 RequestCycles(CyclesRequest),
193 IssueDelegation(DelegationRequest),
194 IssueRoleAttestation(RoleAttestationRequest),
195}
196
197impl From<Request> for RootCapabilityCommand {
198 fn from(value: Request) -> Self {
199 match value {
200 Request::CreateCanister(req) => Self::ProvisionCanister(req),
201 Request::UpgradeCanister(req) => Self::UpgradeCanister(req),
202 Request::RecycleCanister(req) => Self::RecycleCanister(req),
203 Request::Cycles(req) => Self::RequestCycles(req),
204 Request::IssueDelegation(req) => Self::IssueDelegation(req),
205 Request::IssueRoleAttestation(req) => Self::IssueRoleAttestation(req),
206 }
207 }
208}
209
210#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
217pub struct RootRequestMetadata {
218 pub request_id: [u8; 32],
219 pub ttl_seconds: u64,
220}
221
222#[derive(CandidType, Clone, Debug, Deserialize)]
229pub struct CreateCanisterRequest {
230 pub canister_role: CanisterRole,
231 pub parent: CreateCanisterParent,
232 pub extra_arg: Option<Vec<u8>>,
233 #[serde(default)]
234 pub metadata: Option<RootRequestMetadata>,
235}
236
237#[derive(CandidType, Clone, Debug, Deserialize)]
244pub enum CreateCanisterParent {
245 Root,
246 ThisCanister,
248 Parent,
250 Canister(Principal),
251 Index(CanisterRole),
252}
253
254#[derive(CandidType, Clone, Debug, Deserialize)]
261pub struct UpgradeCanisterRequest {
262 pub canister_pid: Principal,
263 #[serde(default)]
264 pub metadata: Option<RootRequestMetadata>,
265}
266
267#[derive(CandidType, Clone, Debug, Deserialize)]
274pub struct RecycleCanisterRequest {
275 pub canister_pid: Principal,
276 #[serde(default)]
277 pub metadata: Option<RootRequestMetadata>,
278}
279
280#[derive(CandidType, Clone, Debug, Deserialize)]
287pub struct CyclesRequest {
288 pub cycles: u128,
289 #[serde(default)]
290 pub metadata: Option<RootRequestMetadata>,
291}
292
293#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
300pub enum Response {
301 CreateCanister(CreateCanisterResponse),
302 UpgradeCanister(UpgradeCanisterResponse),
303 RecycleCanister(RecycleCanisterResponse),
304 Cycles(CyclesResponse),
305 DelegationIssued(DelegationProvisionResponse),
306 RoleAttestationIssued(SignedRoleAttestation),
307}
308
309#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
315pub struct CreateCanisterResponse {
316 pub new_canister_pid: Principal,
317}
318
319#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
325pub struct UpgradeCanisterResponse {}
326
327#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
333pub struct RecycleCanisterResponse {}
334
335#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
341pub struct CyclesResponse {
342 pub cycles_transferred: u128,
343}
344
345#[cfg(test)]
350mod tests {
351 use super::*;
352 use crate::dto::auth::DelegationAudience;
353
354 fn p(id: u8) -> Principal {
355 Principal::from_slice(&[id; 29])
356 }
357
358 fn metadata(id: u8) -> RootRequestMetadata {
359 RootRequestMetadata {
360 request_id: [id; 32],
361 ttl_seconds: 60,
362 }
363 }
364
365 fn requests_with_no_metadata() -> Vec<Request> {
366 vec![
367 Request::create_canister(CreateCanisterRequest {
368 canister_role: CanisterRole::new("app"),
369 parent: CreateCanisterParent::Root,
370 extra_arg: None,
371 metadata: None,
372 }),
373 Request::upgrade_canister(UpgradeCanisterRequest {
374 canister_pid: p(2),
375 metadata: None,
376 }),
377 Request::recycle_canister(RecycleCanisterRequest {
378 canister_pid: p(7),
379 metadata: None,
380 }),
381 Request::cycles(CyclesRequest {
382 cycles: 100,
383 metadata: None,
384 }),
385 Request::issue_delegation(DelegationRequest {
386 shard_pid: p(3),
387 scopes: vec!["rpc:verify".to_string()],
388 aud: DelegationAudience::Roles(vec![CanisterRole::new("app")]),
389 ttl_secs: 60,
390 shard_public_key_sec1: vec![1, 2, 3],
391 metadata: None,
392 }),
393 Request::issue_role_attestation(RoleAttestationRequest {
394 subject: p(5),
395 role: CanisterRole::new("test"),
396 subnet_id: None,
397 audience: Some(p(6)),
398 ttl_secs: 60,
399 epoch: 0,
400 metadata: None,
401 }),
402 ]
403 }
404
405 #[test]
406 fn request_family_matches_all_variants() {
407 let families: Vec<RequestFamily> = requests_with_no_metadata()
408 .iter()
409 .map(Request::family)
410 .collect();
411 assert_eq!(
412 families,
413 vec![
414 RequestFamily::Provision,
415 RequestFamily::Upgrade,
416 RequestFamily::RecycleCanister,
417 RequestFamily::RequestCycles,
418 RequestFamily::IssueDelegation,
419 RequestFamily::IssueRoleAttestation,
420 ]
421 );
422 }
423
424 #[test]
425 fn with_metadata_and_without_metadata_cover_all_variants() {
426 let replay_meta = metadata(7);
427
428 for request in requests_with_no_metadata() {
429 let with_meta = request.clone().with_metadata(replay_meta);
430 assert_eq!(
431 with_meta.metadata(),
432 Some(replay_meta),
433 "with_metadata must set metadata for every request variant"
434 );
435
436 let without_meta = with_meta.without_metadata();
437 assert_eq!(
438 without_meta.metadata(),
439 None,
440 "without_metadata must strip metadata for every request variant"
441 );
442 }
443 }
444
445 #[test]
446 fn upgrade_request_is_only_available_for_upgrade_variant() {
447 let upgrade = Request::upgrade_canister(UpgradeCanisterRequest {
448 canister_pid: p(9),
449 metadata: Some(metadata(9)),
450 });
451 assert!(upgrade.upgrade_request().is_some());
452
453 for request in requests_with_no_metadata() {
454 if !matches!(request, Request::UpgradeCanister(_)) {
455 assert!(request.upgrade_request().is_none());
456 }
457 }
458 }
459}