use crate::{CollectionError, CollectionResult};
use alloc::vec::Vec;
use core::ops::Deref;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct BoundedVec<T, const MIN: usize, const MAX: usize>(Vec<T>);
impl<T, const MIN: usize, const MAX: usize> BoundedVec<T, MIN, MAX> {
pub fn new(vec: Vec<T>) -> CollectionResult<Self> {
if MIN > MAX {
return Err(CollectionError::InvalidBounds { min: MIN, max: MAX });
}
let actual = vec.len();
if actual < MIN {
return Err(CollectionError::TooFew { min: MIN, actual });
}
if actual > MAX {
return Err(CollectionError::TooMany { max: MAX, actual });
}
Ok(Self(vec))
}
pub fn push(&mut self, item: T) -> CollectionResult<()> {
if self.0.len() >= MAX {
return Err(CollectionError::TooMany {
max: MAX,
actual: self.0.len().saturating_add(1),
});
}
self.0.push(item);
Ok(())
}
pub fn pop(&mut self) -> CollectionResult<T> {
if self.0.is_empty() {
return Err(CollectionError::TooFew {
min: MIN,
actual: 0,
});
}
let after_pop = self.0.len() - 1;
if after_pop < MIN {
return Err(CollectionError::TooFew {
min: MIN,
actual: after_pop,
});
}
Ok(self.0.pop().unwrap())
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn first(&self) -> Option<&T> {
self.0.first()
}
pub fn last(&self) -> Option<&T> {
self.0.last()
}
pub fn as_slice(&self) -> &[T] {
&self.0
}
pub fn into_inner(self) -> Vec<T> {
self.0
}
pub fn iter(&self) -> core::slice::Iter<'_, T> {
self.0.iter()
}
pub const fn min_len() -> usize {
MIN
}
pub const fn max_len() -> usize {
MAX
}
}
impl<T, const MIN: usize, const MAX: usize> Deref for BoundedVec<T, MIN, MAX> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl<T, const MIN: usize, const MAX: usize> TryFrom<Vec<T>> for BoundedVec<T, MIN, MAX> {
type Error = CollectionError;
fn try_from(vec: Vec<T>) -> Result<Self, Self::Error> {
Self::new(vec)
}
}
impl<T, const MIN: usize, const MAX: usize> From<BoundedVec<T, MIN, MAX>> for Vec<T> {
fn from(value: BoundedVec<T, MIN, MAX>) -> Self {
value.into_inner()
}
}
#[cfg(test)]
mod tests {
use super::BoundedVec;
use crate::CollectionError;
#[test]
fn accepts_valid_length() {
let v = BoundedVec::<i32, 1, 5>::new(alloc::vec![1, 2, 3]).unwrap();
assert_eq!(v.len(), 3);
assert!(!v.is_empty());
}
#[test]
fn rejects_too_few() {
assert_eq!(
BoundedVec::<i32, 2, 5>::new(alloc::vec![1]).unwrap_err(),
CollectionError::TooFew { min: 2, actual: 1 }
);
}
#[test]
fn rejects_too_many() {
assert_eq!(
BoundedVec::<i32, 1, 3>::new(alloc::vec![1, 2, 3, 4]).unwrap_err(),
CollectionError::TooMany { max: 3, actual: 4 }
);
}
#[test]
fn rejects_invalid_bounds() {
assert_eq!(
BoundedVec::<i32, 5, 3>::new(alloc::vec![]).unwrap_err(),
CollectionError::InvalidBounds { min: 5, max: 3 }
);
}
#[test]
fn push_within_capacity() {
let mut v = BoundedVec::<i32, 0, 3>::new(alloc::vec![1, 2]).unwrap();
assert!(v.push(3).is_ok());
assert_eq!(v.len(), 3);
}
#[test]
fn push_at_capacity_returns_error() {
let mut v = BoundedVec::<i32, 0, 2>::new(alloc::vec![1, 2]).unwrap();
assert_eq!(
v.push(3).unwrap_err(),
CollectionError::TooMany { max: 2, actual: 3 }
);
}
#[test]
fn pop_above_minimum() {
let mut v = BoundedVec::<i32, 1, 5>::new(alloc::vec![1, 2, 3]).unwrap();
assert_eq!(v.pop().unwrap(), 3);
assert_eq!(v.len(), 2);
}
#[test]
fn pop_at_minimum_returns_error() {
let mut v = BoundedVec::<i32, 2, 5>::new(alloc::vec![1, 2]).unwrap();
assert!(v.pop().is_err());
}
#[test]
fn first_and_last() {
let v = BoundedVec::<i32, 1, 5>::new(alloc::vec![10, 20, 30]).unwrap();
assert_eq!(v.first(), Some(&10));
assert_eq!(v.last(), Some(&30));
}
#[test]
fn deref_to_slice() {
let v = BoundedVec::<i32, 1, 5>::new(alloc::vec![1, 2, 3]).unwrap();
assert_eq!(&v[..], &[1, 2, 3]);
}
#[test]
fn into_inner() {
let v = BoundedVec::<i32, 1, 5>::new(alloc::vec![1, 2]).unwrap();
assert_eq!(v.into_inner(), alloc::vec![1, 2]);
}
#[test]
fn from_into_vec() {
let v = BoundedVec::<i32, 1, 5>::new(alloc::vec![1, 2]).unwrap();
let inner: alloc::vec::Vec<i32> = alloc::vec::Vec::from(v);
assert_eq!(inner, alloc::vec![1, 2]);
}
#[test]
fn try_from_vec() {
assert!(BoundedVec::<i32, 1, 3>::try_from(alloc::vec![1]).is_ok());
assert!(BoundedVec::<i32, 1, 3>::try_from(alloc::vec![]).is_err());
}
#[test]
fn iter() {
let v = BoundedVec::<i32, 1, 5>::new(alloc::vec![1, 2, 3]).unwrap();
let sum: i32 = v.iter().sum();
assert_eq!(sum, 6);
}
#[test]
fn min_max_len_constants() {
assert_eq!(BoundedVec::<i32, 2, 8>::min_len(), 2);
assert_eq!(BoundedVec::<i32, 2, 8>::max_len(), 8);
}
#[test]
fn zero_min_allows_empty() {
let v = BoundedVec::<i32, 0, 5>::new(alloc::vec![]).unwrap();
assert!(v.is_empty());
assert_eq!(v.first(), None);
assert_eq!(v.last(), None);
}
#[test]
fn pop_min_zero_empty_vec_returns_error() {
let mut v = BoundedVec::<i32, 0, 5>::new(alloc::vec![]).unwrap();
let err = v.pop().unwrap_err();
assert_eq!(err, CollectionError::TooFew { min: 0, actual: 0 });
}
#[test]
fn pop_min_zero_nonempty_succeeds() {
let mut v = BoundedVec::<i32, 0, 5>::new(alloc::vec![1, 2]).unwrap();
assert_eq!(v.pop().unwrap(), 2);
assert_eq!(v.pop().unwrap(), 1);
assert!(v.pop().is_err());
}
#[test]
fn min_equals_max_exact_size() {
assert!(BoundedVec::<i32, 3, 3>::new(alloc::vec![1, 2, 3]).is_ok());
assert!(BoundedVec::<i32, 3, 3>::new(alloc::vec![1, 2]).is_err());
assert!(BoundedVec::<i32, 3, 3>::new(alloc::vec![1, 2, 3, 4]).is_err());
}
}