use crate::{db::commit::marker::COMMIT_LABEL, error::InternalError};
use canic_cdk::structures::{DefaultMemoryImpl, memory::VirtualMemory};
use canic_memory::api::{MemoryApi, MemoryInspection};
use std::sync::OnceLock;
static COMMIT_STORE_ID: OnceLock<u8> = OnceLock::new();
pub(super) fn commit_memory_id() -> Result<u8, InternalError> {
COMMIT_STORE_ID
.get()
.copied()
.ok_or_else(InternalError::commit_memory_id_unconfigured)
}
pub(in crate::db::commit) fn configure_commit_memory_id(
memory_id: u8,
) -> Result<u8, InternalError> {
if let Some(cached_id) = COMMIT_STORE_ID.get() {
if *cached_id != memory_id {
return Err(InternalError::commit_memory_id_mismatch(
*cached_id, memory_id,
));
}
return Ok(*cached_id);
}
MemoryApi::bootstrap_pending().map_err(InternalError::commit_memory_registry_init_failed)?;
validate_commit_slot_registration(memory_id)?;
let _ = COMMIT_STORE_ID.set(memory_id);
Ok(memory_id)
}
pub(super) fn commit_memory_handle() -> Result<VirtualMemory<DefaultMemoryImpl>, InternalError> {
let memory_id = commit_memory_id()?;
let owner = owner_for_memory_id(memory_id)?;
MemoryApi::register(memory_id, &owner, COMMIT_LABEL)
.map_err(InternalError::commit_memory_id_registration_failed)
}
fn validate_commit_slot_registration(memory_id: u8) -> Result<(), InternalError> {
let mut commit_ids = MemoryApi::registered()
.into_iter()
.filter_map(|entry| (entry.label == COMMIT_LABEL).then_some(entry.id))
.collect::<Vec<_>>();
commit_ids.sort_unstable();
commit_ids.dedup();
match commit_ids.as_slice() {
[] => register_commit_slot(memory_id),
[registered_id] if *registered_id == memory_id => Ok(()),
[registered_id] => Err(InternalError::configured_commit_memory_id_mismatch(
memory_id,
*registered_id,
)),
_ => Err(InternalError::multiple_commit_memory_ids_registered(
commit_ids,
)),
}
}
fn register_commit_slot(memory_id: u8) -> Result<(), InternalError> {
let inspection = inspect_memory(memory_id)?;
if let Some(label) = inspection.label.as_deref() {
return Err(InternalError::commit_memory_id_already_registered(
memory_id, label,
));
}
MemoryApi::register(memory_id, &inspection.owner, COMMIT_LABEL)
.map_err(InternalError::commit_memory_id_registration_failed)?;
Ok(())
}
fn owner_for_memory_id(memory_id: u8) -> Result<String, InternalError> {
Ok(inspect_memory(memory_id)?.owner)
}
fn inspect_memory(memory_id: u8) -> Result<MemoryInspection, InternalError> {
MemoryApi::inspect(memory_id)
.ok_or_else(|| InternalError::commit_memory_id_outside_reserved_ranges(memory_id))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::{ErrorClass, ErrorOrigin};
#[test]
fn owner_for_memory_inspection_returns_matching_owner() {
let inspection = MemoryInspection {
id: 12,
owner: "b".to_string(),
range: canic_memory::registry::MemoryRange { start: 11, end: 20 },
label: None,
};
let owner = inspection.owner;
assert_eq!(owner, "b");
}
#[test]
fn inspect_memory_missing_id_maps_to_out_of_range_error() {
let err = MemoryApi::inspect(30)
.ok_or_else(|| InternalError::commit_memory_id_outside_reserved_ranges(30))
.expect_err("id outside ranges must fail");
assert_eq!(err.class, ErrorClass::Unsupported);
assert_eq!(err.origin, ErrorOrigin::Store);
}
}