use std::collections::HashMap;
use std::hash::Hash;
pub(crate) type Slot = usize;
#[derive(Debug)]
pub(crate) struct Slots<T> {
assigned: HashMap<T, Slot>,
free: Vec<Slot>,
}
impl<T> Slots<T>
where
T: Hash + Eq,
{
pub fn new(capacity: usize) -> Self {
Self {
assigned: HashMap::with_capacity(capacity),
free: (0..capacity).rev().collect(),
}
}
pub fn get(&self, entry: &T) -> Option<Slot> {
self.assigned.get(entry).copied()
}
#[cfg(test)]
pub fn contains(&self, entry: &T) -> bool {
self.get(entry).is_some()
}
pub fn assign(&mut self, entry: T) -> Option<Slot> {
if let Some(&slot) = self.assigned.get(&entry) {
return Some(slot);
}
let slot = self.free.pop()?;
self.assigned.insert(entry, slot);
Some(slot)
}
pub fn release(&mut self, entry: &T) -> Option<Slot> {
if let Some(slot) = self.assigned.remove(entry) {
self.free.push(slot);
return Some(slot);
}
None
}
pub fn assigned(&self) -> usize {
self.assigned.len()
}
pub fn available(&self) -> usize {
self.free.len()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AgentInfo {
pub moniker: String,
pub address: String,
}
pub fn parse_agent_version(agent_version: &str) -> AgentInfo {
let mut moniker = String::from("unknown");
let mut address = String::from("unknown");
for part in agent_version.split(',') {
let part = part.trim();
if let Some(mon) = part.strip_prefix("moniker=") {
moniker = mon.to_string();
} else if let Some(addr) = part.strip_prefix("address=") {
address = addr.to_string();
}
}
AgentInfo { moniker, address }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_initial_state() {
let slots: Slots<i32> = Slots::new(5);
assert_eq!(slots.assigned(), 0);
assert_eq!(slots.available(), 5);
}
#[test]
fn test_sequential_assignment() {
let mut slots = Slots::new(3);
assert_eq!(slots.assign(10), Some(0));
assert_eq!(slots.assign(20), Some(1));
assert_eq!(slots.assign(30), Some(2));
assert_eq!(slots.assigned(), 3);
assert_eq!(slots.available(), 0);
}
#[test]
fn test_capacity_limit() {
let mut slots = Slots::new(2);
assert_eq!(slots.assign("A"), Some(0));
assert_eq!(slots.assign("B"), Some(1));
assert_eq!(slots.assign("C"), None, "Should return None when full");
assert_eq!(slots.assigned(), 2);
assert!(!slots.contains(&"C"));
}
#[test]
fn test_idempotent_assignment() {
let mut slots = Slots::new(5);
let slot_a = slots.assign('A').unwrap();
assert_eq!(slot_a, 0);
assert_eq!(slots.assigned(), 1);
let slot_a_again = slots.assign('A').unwrap();
assert_eq!(slot_a, slot_a_again);
assert_eq!(
slots.assigned(),
1,
"Assigned count should not increase on re-assignment"
);
assert_eq!(slots.available(), 4);
}
#[test]
fn test_lookup_methods() {
let mut slots = Slots::new(5);
slots.assign(100);
assert_eq!(slots.get(&100), Some(0));
assert_eq!(slots.get(&999), None);
assert!(slots.contains(&100));
assert!(!slots.contains(&999));
}
#[test]
fn test_release() {
let mut slots = Slots::new(5);
slots.assign(10);
assert_eq!(slots.assigned(), 1);
let freed_slot = slots.release(&10);
assert_eq!(freed_slot, Some(0));
assert_eq!(slots.assigned(), 0);
assert_eq!(slots.available(), 5);
assert!(!slots.contains(&10));
assert_eq!(slots.release(&999), None);
}
#[test]
fn test_recycling_lifo_behavior() {
let mut slots = Slots::new(3);
slots.assign("A"); slots.assign("B"); slots.assign("C");
slots.release(&"B");
slots.release(&"A");
assert_eq!(slots.assign("D"), Some(0));
assert_eq!(slots.assign("E"), Some(1));
}
#[test]
fn test_zero_capacity() {
let mut slots: Slots<i32> = Slots::new(0);
assert_eq!(slots.available(), 0);
assert_eq!(slots.assign(1), None);
}
#[test]
fn test_complex_lifecycle_scenario() {
let mut slots = Slots::new(3);
slots.assign(10); slots.assign(20);
slots.release(&10);
assert_eq!(slots.assign(30), Some(0));
assert_eq!(slots.assign(40), Some(2));
assert_eq!(slots.assign(50), None);
slots.release(&30);
assert_eq!(slots.assign(50), Some(0));
assert_eq!(slots.get(&20), Some(1));
}
#[test]
fn test_parse_agent_version() {
let test_cases = [
("moniker=node1,address=abc123", "node1", "abc123"),
("address=abc123,moniker=node1", "node1", "abc123"), ("moniker=node1", "node1", "unknown"),
("address=abc123", "unknown", "abc123"),
("", "unknown", "unknown"),
(" moniker=node1 , address=abc123 ", "node1", "abc123"), ("invalid_format", "unknown", "unknown"),
];
for (input, expected_moniker, expected_address) in test_cases {
let result = parse_agent_version(input);
assert_eq!(
result.moniker, expected_moniker,
"Failed for input: {:?}",
input
);
assert_eq!(
result.address, expected_address,
"Failed for input: {:?}",
input
);
}
}
}