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 Cycles(CyclesRequest),
20 IssueDelegation(DelegationRequest),
21 IssueRoleAttestation(RoleAttestationRequest),
22}
23
24impl Request {
25 #[must_use]
29 pub const fn create_canister(request: CreateCanisterRequest) -> Self {
30 Self::CreateCanister(request)
31 }
32
33 #[must_use]
37 pub const fn upgrade_canister(request: UpgradeCanisterRequest) -> Self {
38 Self::UpgradeCanister(request)
39 }
40
41 #[must_use]
45 pub const fn cycles(request: CyclesRequest) -> Self {
46 Self::Cycles(request)
47 }
48
49 #[must_use]
53 pub const fn issue_delegation(request: DelegationRequest) -> Self {
54 Self::IssueDelegation(request)
55 }
56
57 #[must_use]
61 pub const fn issue_role_attestation(request: RoleAttestationRequest) -> Self {
62 Self::IssueRoleAttestation(request)
63 }
64
65 #[must_use]
69 pub const fn family(&self) -> RequestFamily {
70 match self {
71 Self::CreateCanister(_) => RequestFamily::Provision,
72 Self::UpgradeCanister(_) => RequestFamily::Upgrade,
73 Self::Cycles(_) => RequestFamily::RequestCycles,
74 Self::IssueDelegation(_) => RequestFamily::IssueDelegation,
75 Self::IssueRoleAttestation(_) => RequestFamily::IssueRoleAttestation,
76 }
77 }
78
79 #[must_use]
83 pub const fn metadata(&self) -> Option<RootRequestMetadata> {
84 match self {
85 Self::CreateCanister(req) => req.metadata,
86 Self::UpgradeCanister(req) => req.metadata,
87 Self::Cycles(req) => req.metadata,
88 Self::IssueDelegation(req) => req.metadata,
89 Self::IssueRoleAttestation(req) => req.metadata,
90 }
91 }
92
93 #[must_use]
97 pub const fn with_metadata(mut self, metadata: RootRequestMetadata) -> Self {
98 match &mut self {
99 Self::CreateCanister(req) => req.metadata = Some(metadata),
100 Self::UpgradeCanister(req) => req.metadata = Some(metadata),
101 Self::Cycles(req) => req.metadata = Some(metadata),
102 Self::IssueDelegation(req) => req.metadata = Some(metadata),
103 Self::IssueRoleAttestation(req) => req.metadata = Some(metadata),
104 }
105 self
106 }
107
108 #[must_use]
112 pub const fn without_metadata(mut self) -> Self {
113 match &mut self {
114 Self::CreateCanister(req) => req.metadata = None,
115 Self::UpgradeCanister(req) => req.metadata = None,
116 Self::Cycles(req) => req.metadata = None,
117 Self::IssueDelegation(req) => req.metadata = None,
118 Self::IssueRoleAttestation(req) => req.metadata = None,
119 }
120 self
121 }
122
123 #[must_use]
127 pub const fn upgrade_request(&self) -> Option<&UpgradeCanisterRequest> {
128 match self {
129 Self::UpgradeCanister(request) => Some(request),
130 _ => None,
131 }
132 }
133}
134
135#[derive(Clone, Copy, Debug, Eq, PartialEq)]
142pub enum RequestFamily {
143 Provision,
144 Upgrade,
145 RequestCycles,
146 IssueDelegation,
147 IssueRoleAttestation,
148}
149
150impl RequestFamily {
151 #[must_use]
155 pub const fn label(self) -> &'static str {
156 match self {
157 Self::Provision => "Provision",
158 Self::Upgrade => "Upgrade",
159 Self::RequestCycles => "RequestCycles",
160 Self::IssueDelegation => "IssueDelegation",
161 Self::IssueRoleAttestation => "IssueRoleAttestation",
162 }
163 }
164}
165
166#[derive(CandidType, Clone, Debug, Deserialize)]
173pub enum RootCapabilityCommand {
174 ProvisionCanister(CreateCanisterRequest),
175 UpgradeCanister(UpgradeCanisterRequest),
176 RequestCycles(CyclesRequest),
177 IssueDelegation(DelegationRequest),
178 IssueRoleAttestation(RoleAttestationRequest),
179}
180
181impl From<Request> for RootCapabilityCommand {
182 fn from(value: Request) -> Self {
183 match value {
184 Request::CreateCanister(req) => Self::ProvisionCanister(req),
185 Request::UpgradeCanister(req) => Self::UpgradeCanister(req),
186 Request::Cycles(req) => Self::RequestCycles(req),
187 Request::IssueDelegation(req) => Self::IssueDelegation(req),
188 Request::IssueRoleAttestation(req) => Self::IssueRoleAttestation(req),
189 }
190 }
191}
192
193#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
200pub struct RootRequestMetadata {
201 pub request_id: [u8; 32],
202 pub ttl_seconds: u64,
203}
204
205#[derive(CandidType, Clone, Debug, Deserialize)]
212pub struct CreateCanisterRequest {
213 pub canister_role: CanisterRole,
214 pub parent: CreateCanisterParent,
215 pub extra_arg: Option<Vec<u8>>,
216 #[serde(default)]
217 pub metadata: Option<RootRequestMetadata>,
218}
219
220#[derive(CandidType, Clone, Debug, Deserialize)]
227pub enum CreateCanisterParent {
228 Root,
229 ThisCanister,
231 Parent,
233 Canister(Principal),
234 Index(CanisterRole),
235}
236
237#[derive(CandidType, Clone, Debug, Deserialize)]
244pub struct UpgradeCanisterRequest {
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 Cycles(CyclesResponse),
274 DelegationIssued(DelegationProvisionResponse),
275 RoleAttestationIssued(SignedRoleAttestation),
276}
277
278#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
284pub struct CreateCanisterResponse {
285 pub new_canister_pid: Principal,
286}
287
288#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
294pub struct UpgradeCanisterResponse {}
295
296#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
302pub struct CyclesResponse {
303 pub cycles_transferred: u128,
304}
305
306#[cfg(test)]
311mod tests {
312 use super::*;
313
314 fn p(id: u8) -> Principal {
315 Principal::from_slice(&[id; 29])
316 }
317
318 fn metadata(id: u8) -> RootRequestMetadata {
319 RootRequestMetadata {
320 request_id: [id; 32],
321 ttl_seconds: 60,
322 }
323 }
324
325 fn requests_with_no_metadata() -> Vec<Request> {
326 vec![
327 Request::create_canister(CreateCanisterRequest {
328 canister_role: CanisterRole::new("app"),
329 parent: CreateCanisterParent::Root,
330 extra_arg: None,
331 metadata: None,
332 }),
333 Request::upgrade_canister(UpgradeCanisterRequest {
334 canister_pid: p(2),
335 metadata: None,
336 }),
337 Request::cycles(CyclesRequest {
338 cycles: 100,
339 metadata: None,
340 }),
341 Request::issue_delegation(DelegationRequest {
342 shard_pid: p(3),
343 scopes: vec!["rpc:verify".to_string()],
344 aud: vec![p(4)],
345 ttl_secs: 60,
346 verifier_targets: vec![],
347 include_root_verifier: false,
348 shard_public_key_sec1: None,
349 metadata: None,
350 }),
351 Request::issue_role_attestation(RoleAttestationRequest {
352 subject: p(5),
353 role: CanisterRole::new("test"),
354 subnet_id: None,
355 audience: Some(p(6)),
356 ttl_secs: 60,
357 epoch: 0,
358 metadata: None,
359 }),
360 ]
361 }
362
363 #[test]
364 fn request_family_matches_all_variants() {
365 let families: Vec<RequestFamily> = requests_with_no_metadata()
366 .iter()
367 .map(Request::family)
368 .collect();
369 assert_eq!(
370 families,
371 vec![
372 RequestFamily::Provision,
373 RequestFamily::Upgrade,
374 RequestFamily::RequestCycles,
375 RequestFamily::IssueDelegation,
376 RequestFamily::IssueRoleAttestation,
377 ]
378 );
379 }
380
381 #[test]
382 fn with_metadata_and_without_metadata_cover_all_variants() {
383 let replay_meta = metadata(7);
384
385 for request in requests_with_no_metadata() {
386 let with_meta = request.clone().with_metadata(replay_meta);
387 assert_eq!(
388 with_meta.metadata(),
389 Some(replay_meta),
390 "with_metadata must set metadata for every request variant"
391 );
392
393 let without_meta = with_meta.without_metadata();
394 assert_eq!(
395 without_meta.metadata(),
396 None,
397 "without_metadata must strip metadata for every request variant"
398 );
399 }
400 }
401
402 #[test]
403 fn upgrade_request_is_only_available_for_upgrade_variant() {
404 let upgrade = Request::upgrade_canister(UpgradeCanisterRequest {
405 canister_pid: p(9),
406 metadata: Some(metadata(9)),
407 });
408 assert!(upgrade.upgrade_request().is_some());
409
410 for request in requests_with_no_metadata() {
411 if !matches!(request, Request::UpgradeCanister(_)) {
412 assert!(request.upgrade_request().is_none());
413 }
414 }
415 }
416}