1use crate::{
2 cdk::structures::{
3 DefaultMemoryImpl,
4 memory::{MemoryId, VirtualMemory},
5 },
6 manager::MEMORY_MANAGER,
7 registry::{MemoryRange, MemoryRegistry, MemoryRegistryError},
8 runtime::{MemoryRuntimeApi, registry::MemoryRegistryInitSummary},
9};
10
11pub struct MemoryApi;
16
17#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct MemoryInspection {
23 pub id: u8,
24 pub owner: String,
25 pub range: MemoryRange,
26 pub label: Option<String>,
27}
28
29impl MemoryApi {
30 pub fn bootstrap_registry(
32 crate_name: &'static str,
33 start: u8,
34 end: u8,
35 ) -> Result<MemoryRegistryInitSummary, MemoryRegistryError> {
36 MemoryRuntimeApi::bootstrap_registry(crate_name, start, end)
37 }
38
39 pub fn register_memory(
43 id: u8,
44 crate_name: &str,
45 label: &str,
46 ) -> Result<VirtualMemory<DefaultMemoryImpl>, MemoryRegistryError> {
47 if let Some(entry) = MemoryRegistry::get(id)
48 && entry.crate_name == crate_name
49 && entry.label == label
50 {
51 return Ok(open_memory(id));
52 }
53
54 MemoryRegistry::register(id, crate_name, label)?;
55 Ok(open_memory(id))
56 }
57
58 #[must_use]
60 pub fn inspect_memory(id: u8) -> Option<MemoryInspection> {
61 let range = MemoryRegistry::export_range_entries()
62 .into_iter()
63 .find(|entry| entry.range.contains(id))?;
64 let label = MemoryRegistry::get(id).map(|entry| entry.label);
65
66 Some(MemoryInspection {
67 id,
68 owner: range.owner,
69 range: range.range,
70 label,
71 })
72 }
73}
74
75fn open_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
77 MEMORY_MANAGER.with_borrow_mut(|mgr| mgr.get(MemoryId::new(id)))
78}
79
80#[cfg(test)]
85mod tests {
86 use super::*;
87 use crate::registry::{MemoryRegistryError, reset_for_tests};
88
89 #[test]
90 fn register_memory_returns_opened_memory_for_reserved_slot() {
91 reset_for_tests();
92 let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
93
94 let _memory = MemoryApi::register_memory(2, "crate_a", "slot").expect("register memory");
95 }
96
97 #[test]
98 fn register_memory_is_idempotent_for_same_entry() {
99 reset_for_tests();
100 let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
101 let _ = MemoryApi::register_memory(2, "crate_a", "slot").expect("first register succeeds");
102
103 let _ = MemoryApi::register_memory(2, "crate_a", "slot").expect("second register succeeds");
104 }
105
106 #[test]
107 fn register_memory_rejects_unreserved_id() {
108 reset_for_tests();
109
110 let Err(err) = MemoryApi::register_memory(9, "crate_a", "slot") else {
111 panic!("unreserved slot must fail")
112 };
113 assert!(matches!(err, MemoryRegistryError::NoReservedRange { .. }));
114 }
115
116 #[test]
117 fn register_memory_preserves_duplicate_id_error_for_conflicts() {
118 reset_for_tests();
119 let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
120 MemoryApi::register_memory(2, "crate_a", "slot").expect("first register succeeds");
121
122 let Err(err) = MemoryApi::register_memory(2, "crate_a", "other") else {
123 panic!("conflicting duplicate register must fail")
124 };
125 assert!(matches!(err, MemoryRegistryError::DuplicateId(2)));
126 }
127
128 #[test]
129 fn inspect_memory_returns_reserved_owner_without_label() {
130 reset_for_tests();
131 let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
132
133 let inspection = MemoryApi::inspect_memory(2).expect("reserved slot should inspect");
134 assert_eq!(inspection.owner, "crate_a");
135 assert_eq!(inspection.range, MemoryRange { start: 1, end: 3 });
136 assert_eq!(inspection.label, None);
137 }
138
139 #[test]
140 fn inspect_memory_returns_registered_label() {
141 reset_for_tests();
142 let _ = MemoryApi::bootstrap_registry("crate_a", 1, 3).expect("bootstrap registry");
143 let _ = MemoryApi::register_memory(2, "crate_a", "slot").expect("register memory");
144
145 let inspection = MemoryApi::inspect_memory(2).expect("registered slot should inspect");
146 assert_eq!(inspection.owner, "crate_a");
147 assert_eq!(inspection.range, MemoryRange { start: 1, end: 3 });
148 assert_eq!(inspection.label.as_deref(), Some("slot"));
149 }
150
151 #[test]
152 fn inspect_memory_returns_none_for_unowned_id() {
153 reset_for_tests();
154 assert_eq!(MemoryApi::inspect_memory(9), None);
155 }
156}