1use crate::dto::{
2 auth::{
3 DelegationProvisionResponse, DelegationRequest, RoleAttestationRequest,
4 SignedRoleAttestation,
5 },
6 prelude::*,
7};
8
9#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
15pub enum Request {
16 CreateCanister(CreateCanisterRequest),
17 UpgradeCanister(UpgradeCanisterRequest),
18 Cycles(CyclesRequest),
19 IssueDelegation(DelegationRequest),
20 IssueRoleAttestation(RoleAttestationRequest),
21}
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum RequestFamily {
30 Provision,
31 Upgrade,
32 RequestCycles,
33 IssueDelegation,
34 IssueRoleAttestation,
35}
36
37impl RequestFamily {
38 #[must_use]
42 pub const fn label(self) -> &'static str {
43 match self {
44 Self::Provision => "Provision",
45 Self::Upgrade => "Upgrade",
46 Self::RequestCycles => "RequestCycles",
47 Self::IssueDelegation => "IssueDelegation",
48 Self::IssueRoleAttestation => "IssueRoleAttestation",
49 }
50 }
51}
52
53impl Request {
54 #[must_use]
58 pub const fn create_canister(request: CreateCanisterRequest) -> Self {
59 Self::CreateCanister(request)
60 }
61
62 #[must_use]
66 pub const fn upgrade_canister(request: UpgradeCanisterRequest) -> Self {
67 Self::UpgradeCanister(request)
68 }
69
70 #[must_use]
74 pub const fn cycles(request: CyclesRequest) -> Self {
75 Self::Cycles(request)
76 }
77
78 #[must_use]
82 pub const fn issue_delegation(request: DelegationRequest) -> Self {
83 Self::IssueDelegation(request)
84 }
85
86 #[must_use]
90 pub const fn issue_role_attestation(request: RoleAttestationRequest) -> Self {
91 Self::IssueRoleAttestation(request)
92 }
93
94 #[must_use]
98 pub const fn family(&self) -> RequestFamily {
99 match self {
100 Self::CreateCanister(_) => RequestFamily::Provision,
101 Self::UpgradeCanister(_) => RequestFamily::Upgrade,
102 Self::Cycles(_) => RequestFamily::RequestCycles,
103 Self::IssueDelegation(_) => RequestFamily::IssueDelegation,
104 Self::IssueRoleAttestation(_) => RequestFamily::IssueRoleAttestation,
105 }
106 }
107
108 #[must_use]
112 pub const fn metadata(&self) -> Option<RootRequestMetadata> {
113 match self {
114 Self::CreateCanister(req) => req.metadata,
115 Self::UpgradeCanister(req) => req.metadata,
116 Self::Cycles(req) => req.metadata,
117 Self::IssueDelegation(req) => req.metadata,
118 Self::IssueRoleAttestation(req) => req.metadata,
119 }
120 }
121
122 #[must_use]
126 pub const fn with_metadata(mut self, metadata: RootRequestMetadata) -> Self {
127 match &mut self {
128 Self::CreateCanister(req) => req.metadata = Some(metadata),
129 Self::UpgradeCanister(req) => req.metadata = Some(metadata),
130 Self::Cycles(req) => req.metadata = Some(metadata),
131 Self::IssueDelegation(req) => req.metadata = Some(metadata),
132 Self::IssueRoleAttestation(req) => req.metadata = Some(metadata),
133 }
134 self
135 }
136
137 #[must_use]
141 pub const fn without_metadata(mut self) -> Self {
142 match &mut self {
143 Self::CreateCanister(req) => req.metadata = None,
144 Self::UpgradeCanister(req) => req.metadata = None,
145 Self::Cycles(req) => req.metadata = None,
146 Self::IssueDelegation(req) => req.metadata = None,
147 Self::IssueRoleAttestation(req) => req.metadata = None,
148 }
149 self
150 }
151
152 #[must_use]
156 pub const fn upgrade_request(&self) -> Option<&UpgradeCanisterRequest> {
157 match self {
158 Self::UpgradeCanister(request) => Some(request),
159 _ => None,
160 }
161 }
162}
163
164#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
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, Serialize)]
196pub struct RootRequestMetadata {
197 pub request_id: [u8; 32],
198 pub ttl_seconds: u64,
199}
200
201#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
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, Serialize)]
221pub enum CreateCanisterParent {
222 Root,
223 ThisCanister,
225 Parent,
227 Canister(Principal),
228 Directory(CanisterRole),
229}
230
231#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
237pub struct UpgradeCanisterRequest {
238 pub canister_pid: Principal,
239 #[serde(default)]
240 pub metadata: Option<RootRequestMetadata>,
241}
242
243#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
249pub struct CyclesRequest {
250 pub cycles: u128,
251 #[serde(default)]
252 pub metadata: Option<RootRequestMetadata>,
253}
254
255#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
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, Serialize)]
275pub struct CreateCanisterResponse {
276 pub new_canister_pid: Principal,
277}
278
279#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
285pub struct UpgradeCanisterResponse {}
286
287#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
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}