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