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}
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 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#[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); 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}