Skip to main content

icydb_schema/node/
canister.rs

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///
9/// Canister
10///
11
12#[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        // Check all Store nodes for this canister
113        for (path, store) in schema.filter_nodes::<Store>(|node| node.canister() == canister_path) {
114            match store.storage() {
115                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//
233// TESTS
234//
235
236#[cfg(test)]
237mod tests;