use alloc::collections::BTreeMap;
use alloc::format;
use crate::error::CodedError;
pub const INVALID_HANDLE: &str = "INVALID_HANDLE";
pub struct HandleRegistry<T> {
resource: &'static str,
next: i32,
entries: BTreeMap<i32, T>,
}
impl<T> HandleRegistry<T> {
pub const fn new(resource: &'static str) -> Self {
Self { resource, next: 0, entries: BTreeMap::new() }
}
pub fn store(&mut self, value: T) -> i32 {
loop {
self.next = self.next.wrapping_add(1).max(1);
if !self.entries.contains_key(&self.next) {
break;
}
}
self.entries.insert(self.next, value);
self.next
}
pub fn with<R>(&self, handle: i32, body: impl FnOnce(&T) -> R) -> Result<R, CodedError> {
self.entries
.get(&handle)
.map(body)
.ok_or_else(|| self.unknown())
}
pub fn with_mut<R>(&mut self, handle: i32, body: impl FnOnce(&mut T) -> R) -> Result<R, CodedError> {
self.entries
.get_mut(&handle)
.map(body)
.ok_or_else(|| self.unknown())
}
pub fn take(&mut self, handle: i32) -> Result<T, CodedError> {
self.entries.remove(&handle).ok_or_else(|| self.unknown())
}
pub fn remove(&mut self, handle: i32) {
self.entries.remove(&handle);
}
fn unknown(&self) -> CodedError {
CodedError::new(INVALID_HANDLE, format!("unknown {} handle", self.resource))
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
#[test]
fn store_returns_distinct_non_zero_handles() {
let mut registry = HandleRegistry::new("thing");
let first = registry.store(1u8);
let second = registry.store(2u8);
assert_ne!(first, 0);
assert_ne!(second, 0);
assert_ne!(first, second);
}
#[test]
fn with_reads_a_stored_value() {
let mut registry = HandleRegistry::new("thing");
let handle = registry.store(7u8);
assert!(matches!(registry.with(handle, |value| *value), Ok(7)));
}
#[test]
fn with_mut_mutates_in_place() {
let mut registry = HandleRegistry::new("thing");
let handle = registry.store(1u8);
assert!(matches!(registry.with_mut(handle, |value| *value += 8), Ok(())));
assert!(matches!(registry.with(handle, |value| *value), Ok(9)));
}
#[test]
fn take_removes_and_returns_the_value() {
let mut registry = HandleRegistry::new("thing");
let handle = registry.store(5u8);
assert!(matches!(registry.take(handle), Ok(5)));
assert!(registry.with(handle, |value| *value).is_err());
}
#[test]
fn an_unknown_handle_is_rejected_with_a_stable_code() {
let registry: HandleRegistry<u8> = HandleRegistry::new("thing");
let code = registry
.with(1, |value| *value)
.err()
.map(|error| error.code);
assert_eq!(code, Some(INVALID_HANDLE.to_string()));
}
#[test]
fn the_error_message_names_the_resource() {
let registry: HandleRegistry<u8> = HandleRegistry::new("thing");
let message = registry
.with(1, |value| *value)
.err()
.map(|error| error.message);
assert_eq!(message, Some("unknown thing handle".to_string()));
}
#[test]
fn remove_frees_the_handle() {
let mut registry = HandleRegistry::new("thing");
let handle = registry.store(1u8);
registry.remove(handle);
assert!(registry.with(handle, |value| *value).is_err());
}
#[test]
fn a_wrapped_counter_skips_zero_and_live_handles() {
let mut registry = HandleRegistry::new("thing");
let first = registry.store(1u8);
registry.next = i32::MAX;
let second = registry.store(2u8);
assert_eq!(first, 1);
assert_eq!(second, 2);
assert!(matches!(registry.with(first, |value| *value), Ok(1)));
}
}