pub(crate) const APP_MEMORY_ID_MIN: u8 = 100;
pub(crate) const APP_MEMORY_ID_MAX: u8 = 254;
const RESERVED_INTERNAL_MEMORY_ID: u8 = u8::MAX;
pub(crate) fn memory_id_out_of_range_error(
label: &str,
memory_id: u8,
min: u8,
max: u8,
) -> Option<String> {
(!(memory_id >= min && memory_id <= max))
.then(|| format!("{label} {memory_id} outside of range {min}-{max}"))
}
pub(crate) fn memory_id_reserved_error(label: &str, memory_id: u8) -> Option<String> {
(memory_id == RESERVED_INTERNAL_MEMORY_ID)
.then(|| format!("{label} {memory_id} is reserved for stable-structures internals"))
}
pub(crate) fn app_memory_id_error(label: &str, memory_id: u8) -> Option<String> {
(!(APP_MEMORY_ID_MIN..=APP_MEMORY_ID_MAX).contains(&memory_id)).then(|| {
format!(
"{label} {memory_id} outside of app-owned stable memory range {APP_MEMORY_ID_MIN}-{APP_MEMORY_ID_MAX}"
)
})
}
#[must_use]
pub(crate) fn stable_key_segment_is_canonical(value: &str) -> bool {
let mut bytes = value.bytes();
bytes.next().is_some_and(|byte| byte.is_ascii_lowercase())
&& bytes.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_')
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn app_memory_id_policy_matches_schema_policy() {
for memory_id in APP_MEMORY_ID_MIN..=APP_MEMORY_ID_MAX {
assert!(
app_memory_id_error("memory_id", memory_id).is_none(),
"derive should accept app memory id {memory_id}",
);
assert!(
memory_id_reserved_error("memory_id", memory_id).is_none(),
"derive should not classify app memory id {memory_id} as reserved",
);
}
for memory_id in [0, APP_MEMORY_ID_MIN - 1] {
assert!(
app_memory_id_error("memory_id", memory_id).is_some(),
"derive should reject below-range app memory id {memory_id}",
);
}
assert!(
app_memory_id_error("memory_id", u8::MAX).is_some(),
"derive should reject reserved id as outside the app-owned range",
);
assert!(
memory_id_reserved_error("memory_id", u8::MAX).is_some(),
"derive should reject reserved id explicitly",
);
}
#[test]
fn stable_key_segment_policy_matches_schema_policy() {
for segment in ["db", "demo_rpg", "store_1", "v1"] {
assert!(
stable_key_segment_is_canonical(segment),
"compiler segment policy must accept valid segment {segment}",
);
assert!(stable_key_segment_is_canonical(segment));
}
for segment in [
"",
"1db",
"_db",
"Demo",
"demo-rpg",
"demo.rpg",
"canic.owned",
] {
assert!(
!stable_key_segment_is_canonical(segment),
"compiler segment policy must reject invalid segment {segment:?}",
);
assert!(!stable_key_segment_is_canonical(segment));
}
}
}