icydb_schema/node/
canister.rs1use crate::prelude::*;
2use std::collections::BTreeMap;
3
4#[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 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#[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); 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}