1use crate::{
2 cdk::structures::{
3 DefaultMemoryImpl,
4 memory::{MemoryId, VirtualMemory},
5 },
6 manager::MEMORY_MANAGER,
7 registry::{
8 MemoryRange, MemoryRegistry, MemoryRegistryError, defer_register, defer_register_with_key,
9 },
10 runtime::{MemoryRuntimeApi, registry::MemoryRegistryRuntime},
11};
12
13pub struct MemoryApi;
20
21#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct MemoryInspection {
28 pub id: u8,
30 pub owner: String,
32 pub range: MemoryRange,
34 pub label: Option<String>,
36 pub stable_key: Option<String>,
38}
39
40#[derive(Clone, Debug, Eq, PartialEq)]
46pub struct RegisteredMemory {
47 pub id: u8,
49 pub owner: String,
51 pub range: MemoryRange,
53 pub label: String,
55 pub stable_key: String,
57}
58
59#[derive(Clone, Debug, Eq, PartialEq)]
65pub struct LedgerSnapshot {
66 pub ranges: Vec<(String, MemoryRange)>,
68 pub entries: Vec<(u8, crate::registry::MemoryRegistryEntry)>,
70}
71
72impl MemoryApi {
73 pub fn bootstrap_owner_range(
75 crate_name: &'static str,
76 start: u8,
77 end: u8,
78 ) -> Result<(), MemoryRegistryError> {
79 let _ = MemoryRuntimeApi::bootstrap_registry(crate_name, start, end)?;
80 Ok(())
81 }
82
83 pub fn bootstrap_pending() -> Result<(), MemoryRegistryError> {
86 let _ = MemoryRuntimeApi::bootstrap_registry_without_range()?;
87 Ok(())
88 }
89
90 pub fn declare(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
94 if MemoryRegistryRuntime::is_initialized() {
95 return Err(MemoryRegistryError::RegistrationAfterBootstrap {
96 ranges: 0,
97 registrations: 1,
98 });
99 }
100
101 defer_register(id, crate_name, label)
102 }
103
104 pub fn declare_with_key(
108 id: u8,
109 crate_name: &str,
110 label: &str,
111 stable_key: &str,
112 ) -> Result<(), MemoryRegistryError> {
113 if MemoryRegistryRuntime::is_initialized() {
114 return Err(MemoryRegistryError::RegistrationAfterBootstrap {
115 ranges: 0,
116 registrations: 1,
117 });
118 }
119
120 defer_register_with_key(id, crate_name, label, stable_key)
121 }
122
123 pub fn register(
128 id: u8,
129 crate_name: &str,
130 label: &str,
131 ) -> Result<VirtualMemory<DefaultMemoryImpl>, MemoryRegistryError> {
132 if !MemoryRegistryRuntime::is_initialized() {
133 return Err(MemoryRegistryError::RegistryNotBootstrapped);
134 }
135
136 if let Some(entry) = MemoryRegistry::get(id)
137 && entry.crate_name == crate_name
138 && entry.label == label
139 {
140 return Ok(open_memory(id));
141 }
142
143 Err(MemoryRegistryError::RegistrationAfterBootstrap {
144 ranges: 0,
145 registrations: 1,
146 })
147 }
148
149 pub fn register_with_key(
151 id: u8,
152 _crate_name: &str,
153 _label: &str,
154 stable_key: &str,
155 ) -> Result<VirtualMemory<DefaultMemoryImpl>, MemoryRegistryError> {
156 if !MemoryRegistryRuntime::is_initialized() {
157 return Err(MemoryRegistryError::RegistryNotBootstrapped);
158 }
159
160 if let Some(entry) = MemoryRegistry::get(id)
161 && entry.stable_key == stable_key
162 {
163 return Ok(open_memory(id));
164 }
165
166 Err(MemoryRegistryError::RegistrationAfterBootstrap {
167 ranges: 0,
168 registrations: 1,
169 })
170 }
171
172 #[must_use]
174 pub fn inspect(id: u8) -> Option<MemoryInspection> {
175 let range = MemoryRegistry::export_range_entries()
176 .into_iter()
177 .find(|entry| entry.range.contains(id))?;
178 let entry = MemoryRegistry::get(id);
179 let label = entry.as_ref().map(|entry| entry.label.clone());
180 let stable_key = entry.map(|entry| entry.stable_key);
181
182 Some(MemoryInspection {
183 id,
184 owner: range.owner,
185 range: range.range,
186 label,
187 stable_key,
188 })
189 }
190
191 #[must_use]
193 pub fn registered() -> Vec<RegisteredMemory> {
194 MemoryRegistry::export_ids_by_range()
195 .into_iter()
196 .flat_map(|snapshot| {
197 snapshot
198 .entries
199 .into_iter()
200 .map(move |(id, entry)| RegisteredMemory {
201 id,
202 owner: snapshot.owner.clone(),
203 range: snapshot.range,
204 label: entry.label,
205 stable_key: entry.stable_key,
206 })
207 })
208 .collect()
209 }
210
211 #[must_use]
213 pub fn registered_for_owner(owner: &str) -> Vec<RegisteredMemory> {
214 Self::registered()
215 .into_iter()
216 .filter(|entry| entry.owner == owner)
217 .collect()
218 }
219
220 #[must_use]
222 pub fn find(owner: &str, label: &str) -> Option<RegisteredMemory> {
223 Self::registered()
224 .into_iter()
225 .find(|entry| entry.owner == owner && entry.label == label)
226 }
227
228 pub fn ledger_snapshot() -> Result<LedgerSnapshot, MemoryRegistryError> {
230 Ok(LedgerSnapshot {
231 ranges: MemoryRegistry::try_export_historical_ranges()?,
232 entries: MemoryRegistry::try_export_historical()?,
233 })
234 }
235}
236
237fn open_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
239 MEMORY_MANAGER.with_borrow_mut(|mgr| mgr.get(MemoryId::new(id)))
240}
241
242#[cfg(test)]
247mod tests {
248 use super::*;
249 use crate::registry::{
250 MemoryRegistryError, defer_register, defer_reserve_range, reset_for_tests,
251 };
252
253 #[test]
254 fn register_memory_opens_validated_memory_for_reserved_slot() {
255 reset_for_tests();
256 defer_reserve_range("crate_a", 100, 102).expect("defer range");
257 defer_register(101, "crate_a", "slot").expect("defer register");
258 MemoryApi::bootstrap_pending().expect("bootstrap registry");
259
260 let _memory = MemoryApi::register(101, "crate_a", "slot").expect("open memory");
261 }
262
263 #[test]
264 fn register_memory_is_idempotent_for_same_entry() {
265 reset_for_tests();
266 defer_reserve_range("crate_a", 100, 102).expect("defer range");
267 defer_register(101, "crate_a", "slot").expect("defer register");
268 MemoryApi::bootstrap_pending().expect("bootstrap registry");
269 let _ = MemoryApi::register(101, "crate_a", "slot").expect("first open succeeds");
270
271 let _ = MemoryApi::register(101, "crate_a", "slot").expect("second open succeeds");
272 }
273
274 #[test]
275 fn register_with_key_opens_validated_explicit_key() {
276 reset_for_tests();
277 defer_reserve_range("crate_a", 100, 102).expect("defer range");
278 MemoryApi::declare_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
279 .expect("defer register");
280 MemoryApi::bootstrap_pending().expect("bootstrap registry");
281
282 let _memory = MemoryApi::register_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
283 .expect("open memory");
284 }
285
286 #[test]
287 fn declare_memory_does_not_open_before_bootstrap() {
288 reset_for_tests();
289
290 MemoryApi::declare_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
291 .expect("declare memory");
292
293 assert!(MemoryRegistry::get(101).is_none());
294 }
295
296 #[test]
297 fn declare_memory_rejects_after_bootstrap_seal() {
298 reset_for_tests();
299 MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
300
301 let err = MemoryApi::declare_with_key(101, "crate_a", "slot", "app.crate_a.slot.v1")
302 .expect_err("late declaration should fail");
303 assert!(matches!(
304 err,
305 MemoryRegistryError::RegistrationAfterBootstrap {
306 ranges: 0,
307 registrations: 1,
308 }
309 ));
310 }
311
312 #[test]
313 fn register_memory_rejects_before_bootstrap_validation() {
314 reset_for_tests();
315
316 let Err(err) = MemoryApi::register(100, "crate_a", "slot") else {
317 panic!("opening before bootstrap must fail")
318 };
319 assert!(matches!(err, MemoryRegistryError::RegistryNotBootstrapped));
320 }
321
322 #[test]
323 fn register_memory_rejects_new_claim_after_bootstrap_seal() {
324 reset_for_tests();
325 MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
326
327 let Err(err) = MemoryApi::register(101, "crate_a", "slot") else {
328 panic!("new registration after bootstrap must fail")
329 };
330 assert!(matches!(
331 err,
332 MemoryRegistryError::RegistrationAfterBootstrap {
333 ranges: 0,
334 registrations: 1,
335 }
336 ));
337 }
338
339 #[test]
340 fn bootstrap_pending_flushes_deferred_state() {
341 reset_for_tests();
342 defer_reserve_range("crate_a", 100, 102).expect("defer range");
343 defer_register(101, "crate_a", "slot").expect("defer register");
344
345 MemoryApi::bootstrap_pending().expect("bootstrap pending");
346
347 assert!(MemoryRegistry::export_ranges().contains(&(
348 "crate_a".to_string(),
349 MemoryRange {
350 start: 100,
351 end: 102
352 }
353 )));
354 let entries = MemoryRegistry::export();
355 assert!(entries.iter().any(|(id, entry)| {
356 *id == 101 && entry.crate_name == "crate_a" && entry.label == "slot"
357 }));
358 }
359
360 #[test]
361 fn inspect_memory_returns_reserved_owner_without_label() {
362 reset_for_tests();
363 MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
364
365 let inspection = MemoryApi::inspect(101).expect("reserved slot should inspect");
366 assert_eq!(inspection.owner, "crate_a");
367 assert_eq!(
368 inspection.range,
369 MemoryRange {
370 start: 100,
371 end: 102
372 }
373 );
374 assert_eq!(inspection.label, None);
375 }
376
377 #[test]
378 fn inspect_memory_returns_registered_label() {
379 reset_for_tests();
380 defer_reserve_range("crate_a", 100, 102).expect("defer range");
381 defer_register(101, "crate_a", "slot").expect("defer register");
382 MemoryApi::bootstrap_pending().expect("bootstrap registry");
383
384 let inspection = MemoryApi::inspect(101).expect("registered slot should inspect");
385 assert_eq!(inspection.owner, "crate_a");
386 assert_eq!(
387 inspection.range,
388 MemoryRange {
389 start: 100,
390 end: 102
391 }
392 );
393 assert_eq!(inspection.label.as_deref(), Some("slot"));
394 assert_eq!(
395 inspection.stable_key.as_deref(),
396 Some("legacy.crate_a.slot.v1")
397 );
398 }
399
400 #[test]
401 fn inspect_memory_returns_none_for_unowned_id() {
402 reset_for_tests();
403 assert_eq!(MemoryApi::inspect(99), None);
404 }
405
406 #[test]
407 fn registered_memories_lists_registered_slots_with_owner_context() {
408 reset_for_tests();
409 defer_reserve_range("crate_a", 100, 102).expect("defer range A");
410 defer_reserve_range("crate_b", 110, 112).expect("defer range B");
411 defer_register(101, "crate_a", "slot_a").expect("defer register A");
412 defer_register(111, "crate_b", "slot_b").expect("defer register B");
413 MemoryApi::bootstrap_pending().expect("bootstrap registry");
414
415 let registrations = MemoryApi::registered();
416 assert_eq!(registrations.len(), 3);
417 assert!(registrations.contains(&RegisteredMemory {
418 id: 101,
419 owner: "crate_a".to_string(),
420 range: MemoryRange {
421 start: 100,
422 end: 102
423 },
424 label: "slot_a".to_string(),
425 stable_key: "legacy.crate_a.slot_a.v1".to_string(),
426 }));
427 assert!(registrations.contains(&RegisteredMemory {
428 id: 111,
429 owner: "crate_b".to_string(),
430 range: MemoryRange {
431 start: 110,
432 end: 112
433 },
434 label: "slot_b".to_string(),
435 stable_key: "legacy.crate_b.slot_b.v1".to_string(),
436 }));
437 }
438
439 #[test]
440 fn registered_memories_for_owner_filters_to_owner() {
441 reset_for_tests();
442 defer_reserve_range("crate_a", 100, 102).expect("defer range A");
443 defer_reserve_range("crate_b", 110, 112).expect("defer range B");
444 defer_register(101, "crate_a", "slot_a").expect("defer register A");
445 defer_register(111, "crate_b", "slot_b").expect("defer register B");
446 MemoryApi::bootstrap_pending().expect("bootstrap registry");
447
448 let registrations = MemoryApi::registered_for_owner("crate_a");
449 assert_eq!(
450 registrations,
451 vec![RegisteredMemory {
452 id: 101,
453 owner: "crate_a".to_string(),
454 range: MemoryRange {
455 start: 100,
456 end: 102
457 },
458 label: "slot_a".to_string(),
459 stable_key: "legacy.crate_a.slot_a.v1".to_string(),
460 }]
461 );
462 }
463
464 #[test]
465 fn find_registered_memory_returns_match_for_owner_and_label() {
466 reset_for_tests();
467 defer_reserve_range("crate_a", 100, 102).expect("defer range");
468 defer_register(101, "crate_a", "slot_a").expect("defer register");
469 MemoryApi::bootstrap_pending().expect("bootstrap registry");
470
471 let registration = MemoryApi::find("crate_a", "slot_a").expect("slot should exist");
472 assert_eq!(
473 registration,
474 RegisteredMemory {
475 id: 101,
476 owner: "crate_a".to_string(),
477 range: MemoryRange {
478 start: 100,
479 end: 102
480 },
481 label: "slot_a".to_string(),
482 stable_key: "legacy.crate_a.slot_a.v1".to_string(),
483 }
484 );
485 }
486
487 #[test]
488 fn find_registered_memory_returns_none_when_missing() {
489 reset_for_tests();
490 MemoryApi::bootstrap_owner_range("crate_a", 100, 102).expect("bootstrap registry");
491 assert_eq!(MemoryApi::find("crate_a", "slot_a"), None);
492 }
493
494 #[test]
495 fn ledger_snapshot_reads_historical_records() {
496 reset_for_tests();
497 defer_reserve_range("crate_a", 100, 102).expect("defer range");
498 defer_register(101, "crate_a", "slot").expect("defer register");
499 MemoryApi::bootstrap_pending().expect("bootstrap registry");
500
501 let snapshot = MemoryApi::ledger_snapshot().expect("ledger snapshot");
502 assert!(snapshot.ranges.iter().any(|(owner, range)| {
503 owner == "crate_a"
504 && *range
505 == MemoryRange {
506 start: 100,
507 end: 102,
508 }
509 }));
510 assert!(snapshot.entries.iter().any(|(id, entry)| {
511 *id == 101
512 && entry.crate_name == "crate_a"
513 && entry.label == "slot"
514 && entry.stable_key == "legacy.crate_a.slot.v1"
515 }));
516 }
517}