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    // Failed pool entries were requeued.
100    FailedRequeued { result: PoolBatchResult },
101}
102
103//
104// PoolBatchResult
105//
106
107#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
108pub struct PoolBatchResult {
109    pub total: u64,
110    pub added: u64,
111    pub requeued: u64,
112    pub skipped: u64,
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn reexported_pool_status_roundtrips_through_candid() {
121        let entry = CanisterPoolEntry {
122            pid: Principal::from_slice(&[3; 29]),
123            created_at: 42,
124            cycles: Cycles::new(10_000),
125            status: crate::domain::pool::CanisterPoolStatus::Failed {
126                reason: "bounded reset failure".to_string(),
127            },
128            role: Some(CanisterRole::new("worker")),
129            parent: None,
130            module_hash: Some(vec![1, 2, 3]),
131        };
132
133        let bytes = candid::encode_one(&entry).expect("encode pool entry");
134        let decoded: CanisterPoolEntry = candid::decode_one(&bytes).expect("decode pool entry");
135
136        let dto_status: CanisterPoolStatus = crate::domain::pool::CanisterPoolStatus::Failed {
137            reason: "bounded reset failure".to_string(),
138        };
139
140        assert_eq!(decoded.pid, Principal::from_slice(&[3; 29]));
141        assert_eq!(decoded.created_at, 42);
142        assert_eq!(decoded.cycles, Cycles::new(10_000));
143        assert_eq!(decoded.status, dto_status);
144        assert_eq!(decoded.role, Some(CanisterRole::new("worker")));
145        assert_eq!(decoded.parent, None);
146        assert_eq!(decoded.module_hash, Some(vec![1, 2, 3]));
147    }
148}