Skip to main content

icydb_schema/node/
canister.rs

1use crate::prelude::*;
2use std::collections::BTreeMap;
3
4///
5/// Canister
6///
7
8#[derive(CandidType, Clone, Debug, Serialize)]
9pub struct Canister {
10    pub def: Def,
11    pub memory_min: u8,
12    pub memory_max: u8,
13}
14
15impl MacroNode for Canister {
16    fn as_any(&self) -> &dyn std::any::Any {
17        self
18    }
19}
20
21impl ValidateNode for Canister {
22    fn validate(&self) -> Result<(), ErrorTree> {
23        let mut errs = ErrorTree::new();
24        let schema = schema_read();
25
26        let canister_path = self.def.path();
27        let mut seen_ids = BTreeMap::<u8, String>::new();
28
29        // Check all Store nodes for this canister
30        for (path, store) in schema.filter_nodes::<Store>(|node| node.canister == canister_path) {
31            assert_unique_memory_id(
32                store.data_memory_id,
33                format!("Store `{path}`.data_memory_id"),
34                &canister_path,
35                &mut seen_ids,
36                &mut errs,
37            );
38
39            assert_unique_memory_id(
40                store.index_memory_id,
41                format!("Store `{path}`.index_memory_id"),
42                &canister_path,
43                &mut seen_ids,
44                &mut errs,
45            );
46        }
47
48        errs.result()
49    }
50}
51
52fn assert_unique_memory_id(
53    memory_id: u8,
54    slot: String,
55    canister_path: &str,
56    seen_ids: &mut BTreeMap<u8, String>,
57    errs: &mut ErrorTree,
58) {
59    if let Some(existing) = seen_ids.get(&memory_id) {
60        err!(
61            errs,
62            "duplicate memory_id `{}` used in canister `{}`: {} conflicts with {}",
63            memory_id,
64            canister_path,
65            existing,
66            slot
67        );
68    } else {
69        seen_ids.insert(memory_id, slot);
70    }
71}
72
73impl VisitableNode for Canister {
74    fn route_key(&self) -> String {
75        self.def.path()
76    }
77}
78
79///
80/// TESTS
81///
82
83#[cfg(test)]
84mod tests {
85    use crate::build::schema_write;
86
87    use super::*;
88
89    fn insert_canister(path_module: &'static str, ident: &'static str) -> Canister {
90        let canister = Canister {
91            def: Def {
92                module_path: path_module,
93                ident,
94                comments: None,
95            },
96            memory_min: 0,
97            memory_max: 255,
98        };
99        schema_write().insert_node(SchemaNode::Canister(canister.clone()));
100
101        canister
102    }
103
104    fn insert_store(
105        path_module: &'static str,
106        ident: &'static str,
107        canister_path: &'static str,
108        data_memory_id: u8,
109        index_memory_id: u8,
110    ) {
111        schema_write().insert_node(SchemaNode::Store(Store {
112            def: Def {
113                module_path: path_module,
114                ident,
115                comments: None,
116            },
117            ident,
118            canister: canister_path,
119            data_memory_id,
120            index_memory_id,
121        }));
122    }
123
124    #[test]
125    fn validate_rejects_memory_id_collision_between_stores() {
126        let canister = insert_canister("schema_store_collision", "Canister");
127        let canister_path = "schema_store_collision::Canister";
128
129        insert_store("schema_store_collision", "StoreA", canister_path, 10, 11);
130        insert_store("schema_store_collision", "StoreB", canister_path, 12, 10); // collision
131
132        let err = canister
133            .validate()
134            .expect_err("memory-id collision must fail");
135
136        let rendered = err.to_string();
137        assert!(
138            rendered.contains("duplicate memory_id `10`"),
139            "expected duplicate memory-id error, got: {rendered}"
140        );
141    }
142
143    #[test]
144    fn validate_accepts_unique_memory_ids() {
145        let canister = insert_canister("schema_store_unique", "Canister");
146        let canister_path = "schema_store_unique::Canister";
147
148        insert_store("schema_store_unique", "StoreA", canister_path, 30, 31);
149        insert_store("schema_store_unique", "StoreB", canister_path, 32, 33);
150
151        canister.validate().expect("unique memory IDs should pass");
152    }
153}