1#[cfg(test)]
8mod tests;
9
10use crate::node::{
11 stable_memory_key, validate_app_memory_id, validate_memory_id_in_range,
12 validate_memory_id_not_reserved, validate_stable_key, validate_stable_key_segment,
13};
14use crate::prelude::*;
15use std::collections::BTreeMap;
16
17#[derive(Clone, Debug, Serialize)]
22pub struct Canister {
23 def: Def,
24 memory_namespace: &'static str,
25 memory_min: u8,
26 memory_max: u8,
27 commit_memory_id: u8,
28}
29
30impl Canister {
31 #[must_use]
32 pub const fn new(
33 def: Def,
34 memory_namespace: &'static str,
35 memory_min: u8,
36 memory_max: u8,
37 commit_memory_id: u8,
38 ) -> Self {
39 Self {
40 def,
41 memory_namespace,
42 memory_min,
43 memory_max,
44 commit_memory_id,
45 }
46 }
47
48 #[must_use]
49 pub const fn def(&self) -> &Def {
50 &self.def
51 }
52
53 #[must_use]
54 pub const fn memory_namespace(&self) -> &'static str {
55 self.memory_namespace
56 }
57
58 #[must_use]
59 pub const fn memory_min(&self) -> u8 {
60 self.memory_min
61 }
62
63 #[must_use]
64 pub const fn memory_max(&self) -> u8 {
65 self.memory_max
66 }
67
68 #[must_use]
69 pub const fn commit_memory_id(&self) -> u8 {
70 self.commit_memory_id
71 }
72
73 #[must_use]
74 pub fn commit_stable_key(&self) -> String {
75 stable_memory_key(self.memory_namespace(), "commit", "control")
76 }
77}
78
79impl MacroNode for Canister {
80 fn as_any(&self) -> &dyn std::any::Any {
81 self
82 }
83}
84
85impl ValidateNode for Canister {
86 fn validate(&self) -> Result<(), ErrorTree> {
87 let mut errs = ErrorTree::new();
88
89 let canister_path = self.def().path();
90 let mut seen_ids = BTreeMap::<u8, (String, String)>::new();
91 let mut seen_keys = BTreeMap::<String, (u8, String)>::new();
92
93 validate_stable_key_segment(
94 &mut errs,
95 "canister memory_namespace",
96 self.memory_namespace(),
97 );
98
99 validate_memory_id_in_range(
100 &mut errs,
101 "commit_memory_id",
102 self.commit_memory_id(),
103 self.memory_min(),
104 self.memory_max(),
105 );
106 validate_app_memory_id(&mut errs, "commit_memory_id", self.commit_memory_id());
107 validate_memory_id_not_reserved(&mut errs, "commit_memory_id", self.commit_memory_id());
108 validate_stable_key(&mut errs, "commit stable key", &self.commit_stable_key());
109
110 assert_unique_memory_allocation(
111 self.commit_memory_id(),
112 self.commit_stable_key(),
113 format!("Canister `{}`.commit_memory", self.def().path()),
114 &canister_path,
115 &mut seen_ids,
116 &mut seen_keys,
117 &mut errs,
118 );
119
120 {
121 let schema = schema_read();
122
123 for (path, store) in
125 schema.filter_nodes::<Store>(|node| node.canister() == canister_path)
126 {
127 match store.storage() {
128 StoreStorage::Journaled(_) => {
129 assert_unique_memory_allocation(
130 store
131 .stable_data_allocation(self.memory_namespace())
132 .memory_id(),
133 store
134 .stable_data_allocation(self.memory_namespace())
135 .stable_key()
136 .to_string(),
137 format!("Store `{path}`.data_memory"),
138 &canister_path,
139 &mut seen_ids,
140 &mut seen_keys,
141 &mut errs,
142 );
143
144 assert_unique_memory_allocation(
145 store
146 .stable_index_allocation(self.memory_namespace())
147 .memory_id(),
148 store
149 .stable_index_allocation(self.memory_namespace())
150 .stable_key()
151 .to_string(),
152 format!("Store `{path}`.index_memory"),
153 &canister_path,
154 &mut seen_ids,
155 &mut seen_keys,
156 &mut errs,
157 );
158
159 assert_unique_memory_allocation(
160 store
161 .stable_schema_allocation(self.memory_namespace())
162 .memory_id(),
163 store
164 .stable_schema_allocation(self.memory_namespace())
165 .stable_key()
166 .to_string(),
167 format!("Store `{path}`.schema_memory"),
168 &canister_path,
169 &mut seen_ids,
170 &mut seen_keys,
171 &mut errs,
172 );
173
174 if store.is_journaled_storage() {
175 assert_unique_memory_allocation(
176 store
177 .journal_allocation(self.memory_namespace())
178 .memory_id(),
179 store
180 .journal_allocation(self.memory_namespace())
181 .stable_key()
182 .to_string(),
183 format!("Store `{path}`.journal_memory"),
184 &canister_path,
185 &mut seen_ids,
186 &mut seen_keys,
187 &mut errs,
188 );
189 }
190 }
191 StoreStorage::Heap(_) => {}
192 }
193 }
194 }
195
196 errs.result()
197 }
198}
199
200fn assert_unique_memory_allocation(
201 memory_id: u8,
202 stable_key: String,
203 slot: String,
204 canister_path: &str,
205 seen_ids: &mut BTreeMap<u8, (String, String)>,
206 seen_keys: &mut BTreeMap<String, (u8, String)>,
207 errs: &mut ErrorTree,
208) {
209 if let Some((existing_key, existing_slot)) = seen_ids.get(&memory_id) {
210 err!(
211 errs,
212 "duplicate memory_id `{}` used in canister `{}`: {} ({}) conflicts with {} ({})",
213 memory_id,
214 canister_path,
215 existing_slot,
216 existing_key,
217 slot,
218 stable_key,
219 );
220 } else {
221 seen_ids.insert(memory_id, (stable_key.clone(), slot.clone()));
222 }
223
224 if let Some((existing_id, existing_slot)) = seen_keys.get(&stable_key) {
225 err!(
226 errs,
227 "duplicate stable_key `{}` used in canister `{}`: {} ({}) conflicts with {} ({})",
228 stable_key,
229 canister_path,
230 existing_slot,
231 existing_id,
232 slot,
233 memory_id,
234 );
235 } else {
236 seen_keys.insert(stable_key, (memory_id, slot));
237 }
238}
239
240impl VisitableNode for Canister {
241 fn route_key(&self) -> String {
242 self.def().path()
243 }
244}