1use crate::dto::{
2 auth::{
3 DelegationProvisionResponse, DelegationRequest, RoleAttestationRequest,
4 SignedRoleAttestation,
5 },
6 prelude::*,
7};
8
9#[derive(CandidType, Clone, Debug, Deserialize)]
15pub enum Request {
16 CreateCanister(CreateCanisterRequest),
17 UpgradeCanister(UpgradeCanisterRequest),
18 Cycles(CyclesRequest),
19 IssueDelegation(DelegationRequest),
20 IssueRoleAttestation(RoleAttestationRequest),
21}
22
23impl Request {
24 #[must_use]
28 pub const fn create_canister(request: CreateCanisterRequest) -> Self {
29 Self::CreateCanister(request)
30 }
31
32 #[must_use]
36 pub const fn upgrade_canister(request: UpgradeCanisterRequest) -> Self {
37 Self::UpgradeCanister(request)
38 }
39
40 #[must_use]
44 pub const fn cycles(request: CyclesRequest) -> Self {
45 Self::Cycles(request)
46 }
47
48 #[must_use]
52 pub const fn issue_delegation(request: DelegationRequest) -> Self {
53 Self::IssueDelegation(request)
54 }
55
56 #[must_use]
60 pub const fn issue_role_attestation(request: RoleAttestationRequest) -> Self {
61 Self::IssueRoleAttestation(request)
62 }
63
64 #[must_use]
68 pub const fn family(&self) -> RequestFamily {
69 match self {
70 Self::CreateCanister(_) => RequestFamily::Provision,
71 Self::UpgradeCanister(_) => RequestFamily::Upgrade,
72 Self::Cycles(_) => RequestFamily::RequestCycles,
73 Self::IssueDelegation(_) => RequestFamily::IssueDelegation,
74 Self::IssueRoleAttestation(_) => RequestFamily::IssueRoleAttestation,
75 }
76 }
77
78 #[must_use]
82 pub const fn metadata(&self) -> Option<RootRequestMetadata> {
83 match self {
84 Self::CreateCanister(req) => req.metadata,
85 Self::UpgradeCanister(req) => req.metadata,
86 Self::Cycles(req) => req.metadata,
87 Self::IssueDelegation(req) => req.metadata,
88 Self::IssueRoleAttestation(req) => req.metadata,
89 }
90 }
91
92 #[must_use]
96 pub const fn with_metadata(mut self, metadata: RootRequestMetadata) -> Self {
97 match &mut self {
98 Self::CreateCanister(req) => req.metadata = Some(metadata),
99 Self::UpgradeCanister(req) => req.metadata = Some(metadata),
100 Self::Cycles(req) => req.metadata = Some(metadata),
101 Self::IssueDelegation(req) => req.metadata = Some(metadata),
102 Self::IssueRoleAttestation(req) => req.metadata = Some(metadata),
103 }
104 self
105 }
106
107 #[must_use]
111 pub const fn without_metadata(mut self) -> Self {
112 match &mut self {
113 Self::CreateCanister(req) => req.metadata = None,
114 Self::UpgradeCanister(req) => req.metadata = None,
115 Self::Cycles(req) => req.metadata = None,
116 Self::IssueDelegation(req) => req.metadata = None,
117 Self::IssueRoleAttestation(req) => req.metadata = None,
118 }
119 self
120 }
121
122 #[must_use]
126 pub const fn upgrade_request(&self) -> Option<&UpgradeCanisterRequest> {
127 match self {
128 Self::UpgradeCanister(request) => Some(request),
129 _ => None,
130 }
131 }
132}
133
134#[derive(Clone, Copy, Debug, Eq, PartialEq)]
140pub enum RequestFamily {
141 Provision,
142 Upgrade,
143 RequestCycles,
144 IssueDelegation,
145 IssueRoleAttestation,
146}
147
148impl RequestFamily {
149 #[must_use]
153 pub const fn label(self) -> &'static str {
154 match self {
155 Self::Provision => "Provision",
156 Self::Upgrade => "Upgrade",
157 Self::RequestCycles => "RequestCycles",
158 Self::IssueDelegation => "IssueDelegation",
159 Self::IssueRoleAttestation => "IssueRoleAttestation",
160 }
161 }
162}
163
164#[derive(CandidType, Clone, Debug, Deserialize)]
170pub enum RootCapabilityCommand {
171 ProvisionCanister(CreateCanisterRequest),
172 UpgradeCanister(UpgradeCanisterRequest),
173 RequestCycles(CyclesRequest),
174 IssueDelegation(DelegationRequest),
175 IssueRoleAttestation(RoleAttestationRequest),
176}
177
178impl From<Request> for RootCapabilityCommand {
179 fn from(value: Request) -> Self {
180 match value {
181 Request::CreateCanister(req) => Self::ProvisionCanister(req),
182 Request::UpgradeCanister(req) => Self::UpgradeCanister(req),
183 Request::Cycles(req) => Self::RequestCycles(req),
184 Request::IssueDelegation(req) => Self::IssueDelegation(req),
185 Request::IssueRoleAttestation(req) => Self::IssueRoleAttestation(req),
186 }
187 }
188}
189
190#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
196pub struct RootRequestMetadata {
197 pub request_id: [u8; 32],
198 pub ttl_seconds: u64,
199}
200
201#[derive(CandidType, Clone, Debug, Deserialize)]
207pub struct CreateCanisterRequest {
208 pub canister_role: CanisterRole,
209 pub parent: CreateCanisterParent,
210 pub extra_arg: Option<Vec<u8>>,
211 #[serde(default)]
212 pub metadata: Option<RootRequestMetadata>,
213}
214
215#[derive(CandidType, Clone, Debug, Deserialize)]
221pub enum CreateCanisterParent {
222 Root,
223 ThisCanister,
225 Parent,
227 Canister(Principal),
228 Directory(CanisterRole),
229}
230
231#[derive(CandidType, Clone, Debug, Deserialize)]
237pub struct UpgradeCanisterRequest {
238 pub canister_pid: Principal,
239 #[serde(default)]
240 pub metadata: Option<RootRequestMetadata>,
241}
242
243#[derive(CandidType, Clone, Debug, Deserialize)]
249pub struct CyclesRequest {
250 pub cycles: u128,
251 #[serde(default)]
252 pub metadata: Option<RootRequestMetadata>,
253}
254
255#[derive(CandidType, Clone, Debug, Deserialize)]
261pub enum Response {
262 CreateCanister(CreateCanisterResponse),
263 UpgradeCanister(UpgradeCanisterResponse),
264 Cycles(CyclesResponse),
265 DelegationIssued(DelegationProvisionResponse),
266 RoleAttestationIssued(SignedRoleAttestation),
267}
268
269#[derive(CandidType, Clone, Debug, Deserialize)]
275pub struct CreateCanisterResponse {
276 pub new_canister_pid: Principal,
277}
278
279#[derive(CandidType, Clone, Debug, Deserialize)]
285pub struct UpgradeCanisterResponse {}
286
287#[derive(CandidType, Clone, Debug, Deserialize)]
293pub struct CyclesResponse {
294 pub cycles_transferred: u128,
295}
296
297#[cfg(test)]
302mod tests {
303 use super::*;
304
305 fn p(id: u8) -> Principal {
306 Principal::from_slice(&[id; 29])
307 }
308
309 fn metadata(id: u8) -> RootRequestMetadata {
310 RootRequestMetadata {
311 request_id: [id; 32],
312 ttl_seconds: 60,
313 }
314 }
315
316 fn requests_with_no_metadata() -> Vec<Request> {
317 vec![
318 Request::create_canister(CreateCanisterRequest {
319 canister_role: CanisterRole::new("app"),
320 parent: CreateCanisterParent::Root,
321 extra_arg: None,
322 metadata: None,
323 }),
324 Request::upgrade_canister(UpgradeCanisterRequest {
325 canister_pid: p(2),
326 metadata: None,
327 }),
328 Request::cycles(CyclesRequest {
329 cycles: 100,
330 metadata: None,
331 }),
332 Request::issue_delegation(DelegationRequest {
333 shard_pid: p(3),
334 scopes: vec!["rpc:verify".to_string()],
335 aud: vec![p(4)],
336 ttl_secs: 60,
337 verifier_targets: vec![],
338 include_root_verifier: false,
339 metadata: None,
340 }),
341 Request::issue_role_attestation(RoleAttestationRequest {
342 subject: p(5),
343 role: CanisterRole::new("test"),
344 subnet_id: None,
345 audience: Some(p(6)),
346 ttl_secs: 60,
347 epoch: 0,
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::RequestCycles,
365 RequestFamily::IssueDelegation,
366 RequestFamily::IssueRoleAttestation,
367 ]
368 );
369 }
370
371 #[test]
372 fn with_metadata_and_without_metadata_cover_all_variants() {
373 let replay_meta = metadata(7);
374
375 for request in requests_with_no_metadata() {
376 let with_meta = request.clone().with_metadata(replay_meta);
377 assert_eq!(
378 with_meta.metadata(),
379 Some(replay_meta),
380 "with_metadata must set metadata for every request variant"
381 );
382
383 let without_meta = with_meta.without_metadata();
384 assert_eq!(
385 without_meta.metadata(),
386 None,
387 "without_metadata must strip metadata for every request variant"
388 );
389 }
390 }
391
392 #[test]
393 fn upgrade_request_is_only_available_for_upgrade_variant() {
394 let upgrade = Request::upgrade_canister(UpgradeCanisterRequest {
395 canister_pid: p(9),
396 metadata: Some(metadata(9)),
397 });
398 assert!(upgrade.upgrade_request().is_some());
399
400 for request in requests_with_no_metadata() {
401 if !matches!(request, Request::UpgradeCanister(_)) {
402 assert!(request.upgrade_request().is_none());
403 }
404 }
405 }
406}