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