Skip to main content

canic_core/dto/
pool.rs

1//! Pool admin DTOs.
2//!
3//! This module defines the command and response types used at the
4//! boundary of the pool workflow (endpoints, admin APIs).
5//!
6//! These types:
7//! - are pure data
8//! - contain no logic
9//! - are safe to serialize / expose
10//!
11//! They must NOT:
12//! - perform validation
13//! - call ops or workflow
14//! - embed policy or orchestration logic
15
16use crate::{
17    cdk::types::Cycles,
18    dto::{prelude::*, rpc::RootRequestMetadata},
19};
20
21pub use crate::domain::pool::CanisterPoolStatus;
22
23//
24// CanisterPoolResponse
25// Read-only pool snapshot for endpoints.
26//
27
28#[derive(CandidType, Clone, Debug, Deserialize)]
29pub struct CanisterPoolResponse {
30    pub entries: Vec<CanisterPoolEntry>,
31}
32
33//
34// CanisterPoolEntry
35//
36
37#[derive(CandidType, Clone, Debug, Deserialize)]
38pub struct CanisterPoolEntry {
39    pub pid: Principal,
40    pub created_at: u64,
41    pub cycles: Cycles,
42    pub status: CanisterPoolStatus,
43    pub role: Option<CanisterRole>,
44    pub parent: Option<Principal>,
45    pub module_hash: Option<Vec<u8>>,
46}
47
48//
49// PoolAdminCommand
50//
51// These represent *intent*, not execution.
52// Validation and authorization are handled elsewhere.
53//
54
55#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
56pub enum PoolAdminCommand {
57    // Create a fresh empty pool canister.
58    CreateEmpty(CreateEmptyPoolRequest),
59
60    // Recycle an existing canister back into the pool.
61    Recycle { pid: Principal },
62
63    // Import a canister into the pool immediately (synchronous).
64    ImportImmediate { pid: Principal },
65
66    // Queue one or more canisters for pool import.
67    ImportQueued { pids: Vec<Principal> },
68}
69
70//
71// CreateEmptyPoolRequest
72//
73
74#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
75pub struct CreateEmptyPoolRequest {
76    #[serde(default)]
77    pub metadata: Option<RootRequestMetadata>,
78}
79
80//
81// PoolAdminResponse
82// These describe *what happened*, not *how* it happened.
83//
84
85#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
86pub enum PoolAdminResponse {
87    // A new pool canister was created.
88    Created { pid: Principal },
89
90    // A canister was successfully recycled into the pool.
91    Recycled,
92
93    // A canister was imported immediately.
94    Imported,
95
96    // One or more canisters were queued for import.
97    QueuedImported { result: PoolBatchResult },
98}
99
100//
101// PoolBatchResult
102//
103
104#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
105pub struct PoolBatchResult {
106    pub total: u64,
107    pub added: u64,
108    pub requeued: u64,
109    pub skipped: u64,
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn reexported_pool_status_roundtrips_through_candid() {
118        let entry = CanisterPoolEntry {
119            pid: Principal::from_slice(&[3; 29]),
120            created_at: 42,
121            cycles: Cycles::new(10_000),
122            status: crate::domain::pool::CanisterPoolStatus::Failed {
123                reason: "bounded reset failure".to_string(),
124            },
125            role: Some(CanisterRole::new("worker")),
126            parent: None,
127            module_hash: Some(vec![1, 2, 3]),
128        };
129
130        let bytes = candid::encode_one(&entry).expect("encode pool entry");
131        let decoded: CanisterPoolEntry = candid::decode_one(&bytes).expect("decode pool entry");
132
133        let dto_status: CanisterPoolStatus = crate::domain::pool::CanisterPoolStatus::Failed {
134            reason: "bounded reset failure".to_string(),
135        };
136
137        assert_eq!(decoded.pid, Principal::from_slice(&[3; 29]));
138        assert_eq!(decoded.created_at, 42);
139        assert_eq!(decoded.cycles, Cycles::new(10_000));
140        assert_eq!(decoded.status, dto_status);
141        assert_eq!(decoded.role, Some(CanisterRole::new("worker")));
142        assert_eq!(decoded.parent, None);
143        assert_eq!(decoded.module_hash, Some(vec![1, 2, 3]));
144    }
145}