use crate::{
cdk::structures::{
DefaultMemoryImpl,
memory::{MemoryId, VirtualMemory},
},
manager::MEMORY_MANAGER,
registry::{MemoryRange, MemoryRegistry, MemoryRegistryError},
runtime::MemoryRuntimeApi,
};
pub struct MemoryApi;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MemoryInspection {
pub id: u8,
pub owner: String,
pub range: MemoryRange,
pub label: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RegisteredMemory {
pub id: u8,
pub owner: String,
pub range: MemoryRange,
pub label: String,
}
impl MemoryApi {
pub fn bootstrap_owner_range(
crate_name: &'static str,
start: u8,
end: u8,
) -> Result<(), MemoryRegistryError> {
let _ = MemoryRuntimeApi::bootstrap_registry(crate_name, start, end)?;
Ok(())
}
pub fn bootstrap_pending() -> Result<(), MemoryRegistryError> {
let _ = MemoryRuntimeApi::bootstrap_registry_without_range()?;
Ok(())
}
pub fn register(
id: u8,
crate_name: &str,
label: &str,
) -> Result<VirtualMemory<DefaultMemoryImpl>, MemoryRegistryError> {
if let Some(entry) = MemoryRegistry::get(id)
&& entry.crate_name == crate_name
&& entry.label == label
{
return Ok(open_memory(id));
}
MemoryRegistry::register(id, crate_name, label)?;
Ok(open_memory(id))
}
#[must_use]
pub fn inspect(id: u8) -> Option<MemoryInspection> {
let range = MemoryRegistry::export_range_entries()
.into_iter()
.find(|entry| entry.range.contains(id))?;
let label = MemoryRegistry::get(id).map(|entry| entry.label);
Some(MemoryInspection {
id,
owner: range.owner,
range: range.range,
label,
})
}
#[must_use]
pub fn registered() -> Vec<RegisteredMemory> {
MemoryRegistry::export_ids_by_range()
.into_iter()
.flat_map(|snapshot| {
snapshot
.entries
.into_iter()
.map(move |(id, entry)| RegisteredMemory {
id,
owner: snapshot.owner.clone(),
range: snapshot.range,
label: entry.label,
})
})
.collect()
}
#[must_use]
pub fn registered_for_owner(owner: &str) -> Vec<RegisteredMemory> {
Self::registered()
.into_iter()
.filter(|entry| entry.owner == owner)
.collect()
}
#[must_use]
pub fn find(owner: &str, label: &str) -> Option<RegisteredMemory> {
Self::registered()
.into_iter()
.find(|entry| entry.owner == owner && entry.label == label)
}
}
fn open_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
MEMORY_MANAGER.with_borrow_mut(|mgr| mgr.get(MemoryId::new(id)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::registry::{
MemoryRegistryError, defer_register, defer_reserve_range, reset_for_tests,
};
#[test]
fn register_memory_returns_opened_memory_for_reserved_slot() {
reset_for_tests();
MemoryApi::bootstrap_owner_range("crate_a", 1, 3).expect("bootstrap registry");
let _memory = MemoryApi::register(2, "crate_a", "slot").expect("register memory");
}
#[test]
fn register_memory_is_idempotent_for_same_entry() {
reset_for_tests();
MemoryApi::bootstrap_owner_range("crate_a", 1, 3).expect("bootstrap registry");
let _ = MemoryApi::register(2, "crate_a", "slot").expect("first register succeeds");
let _ = MemoryApi::register(2, "crate_a", "slot").expect("second register succeeds");
}
#[test]
fn register_memory_rejects_unreserved_id() {
reset_for_tests();
let Err(err) = MemoryApi::register(9, "crate_a", "slot") else {
panic!("unreserved slot must fail")
};
assert!(matches!(err, MemoryRegistryError::NoReservedRange { .. }));
}
#[test]
fn register_memory_preserves_duplicate_id_error_for_conflicts() {
reset_for_tests();
MemoryApi::bootstrap_owner_range("crate_a", 1, 3).expect("bootstrap registry");
MemoryApi::register(2, "crate_a", "slot").expect("first register succeeds");
let Err(err) = MemoryApi::register(2, "crate_a", "other") else {
panic!("conflicting duplicate register must fail")
};
assert!(matches!(err, MemoryRegistryError::DuplicateId(2)));
}
#[test]
fn bootstrap_pending_flushes_deferred_state() {
reset_for_tests();
defer_reserve_range("crate_a", 1, 3).expect("defer range");
defer_register(2, "crate_a", "slot").expect("defer register");
MemoryApi::bootstrap_pending().expect("bootstrap pending");
assert_eq!(
MemoryRegistry::export_ranges(),
vec![("crate_a".to_string(), MemoryRange { start: 1, end: 3 })]
);
let entries = MemoryRegistry::export();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].0, 2);
assert_eq!(entries[0].1.crate_name, "crate_a");
assert_eq!(entries[0].1.label, "slot");
}
#[test]
fn inspect_memory_returns_reserved_owner_without_label() {
reset_for_tests();
MemoryApi::bootstrap_owner_range("crate_a", 1, 3).expect("bootstrap registry");
let inspection = MemoryApi::inspect(2).expect("reserved slot should inspect");
assert_eq!(inspection.owner, "crate_a");
assert_eq!(inspection.range, MemoryRange { start: 1, end: 3 });
assert_eq!(inspection.label, None);
}
#[test]
fn inspect_memory_returns_registered_label() {
reset_for_tests();
MemoryApi::bootstrap_owner_range("crate_a", 1, 3).expect("bootstrap registry");
let _ = MemoryApi::register(2, "crate_a", "slot").expect("register memory");
let inspection = MemoryApi::inspect(2).expect("registered slot should inspect");
assert_eq!(inspection.owner, "crate_a");
assert_eq!(inspection.range, MemoryRange { start: 1, end: 3 });
assert_eq!(inspection.label.as_deref(), Some("slot"));
}
#[test]
fn inspect_memory_returns_none_for_unowned_id() {
reset_for_tests();
assert_eq!(MemoryApi::inspect(9), None);
}
#[test]
fn registered_memories_lists_registered_slots_with_owner_context() {
reset_for_tests();
MemoryApi::bootstrap_owner_range("crate_a", 1, 3).expect("bootstrap registry");
MemoryApi::bootstrap_owner_range("crate_b", 10, 12).expect("bootstrap registry");
let _ = MemoryApi::register(2, "crate_a", "slot_a").expect("register memory");
let _ = MemoryApi::register(11, "crate_b", "slot_b").expect("register memory");
let registrations = MemoryApi::registered();
assert_eq!(registrations.len(), 2);
assert!(registrations.contains(&RegisteredMemory {
id: 2,
owner: "crate_a".to_string(),
range: MemoryRange { start: 1, end: 3 },
label: "slot_a".to_string(),
}));
assert!(registrations.contains(&RegisteredMemory {
id: 11,
owner: "crate_b".to_string(),
range: MemoryRange { start: 10, end: 12 },
label: "slot_b".to_string(),
}));
}
#[test]
fn registered_memories_for_owner_filters_to_owner() {
reset_for_tests();
MemoryApi::bootstrap_owner_range("crate_a", 1, 3).expect("bootstrap registry");
MemoryApi::bootstrap_owner_range("crate_b", 10, 12).expect("bootstrap registry");
let _ = MemoryApi::register(2, "crate_a", "slot_a").expect("register memory");
let _ = MemoryApi::register(11, "crate_b", "slot_b").expect("register memory");
let registrations = MemoryApi::registered_for_owner("crate_a");
assert_eq!(
registrations,
vec![RegisteredMemory {
id: 2,
owner: "crate_a".to_string(),
range: MemoryRange { start: 1, end: 3 },
label: "slot_a".to_string(),
}]
);
}
#[test]
fn find_registered_memory_returns_match_for_owner_and_label() {
reset_for_tests();
MemoryApi::bootstrap_owner_range("crate_a", 1, 3).expect("bootstrap registry");
let _ = MemoryApi::register(2, "crate_a", "slot_a").expect("register memory");
let registration = MemoryApi::find("crate_a", "slot_a").expect("slot should exist");
assert_eq!(
registration,
RegisteredMemory {
id: 2,
owner: "crate_a".to_string(),
range: MemoryRange { start: 1, end: 3 },
label: "slot_a".to_string(),
}
);
}
#[test]
fn find_registered_memory_returns_none_when_missing() {
reset_for_tests();
MemoryApi::bootstrap_owner_range("crate_a", 1, 3).expect("bootstrap registry");
assert_eq!(MemoryApi::find("crate_a", "slot_a"), None);
}
}