Files
nomos-node/utils/src/bounded/vec.rs
T

937 lines
30 KiB
Rust

use core::{
marker::PhantomData,
ops::Deref,
slice::{Iter, IterMut},
};
use std::{ops::DerefMut, str::FromStr, vec::IntoIter};
use serde::{
Deserialize, Deserializer,
de::{Error as _, SeqAccess, Visitor},
};
use crate::bounded::{Bounded, BoundedError, BoundedLen};
impl<T> BoundedLen for Vec<T> {
fn bounded_len(&self) -> usize {
self.len()
}
}
/// `Vec<T>` whose length is statically enforced to be in the range `[MIN,
/// MAX]`.
///
/// A thin alias over [`Bounded`]: the length checking and construction
/// machinery lives on the generic wrapper, while sequence deserialization and
/// the operations below are the ones that only make sense for a `Vec`.
///
/// The invariant is enforced at every checked construction site
/// ([`TryFrom<Vec<T>>`](Self::try_from), deserialization), so an instance can
/// never be shorter than `MIN` nor longer than `MAX`.
pub type BoundedVec<T, const MIN: usize, const MAX: usize> = Bounded<Vec<T>, MIN, MAX>;
impl<'de, T, const MIN: usize, const MAX: usize> Deserialize<'de> for Bounded<Vec<T>, MIN, MAX>
where
T: Deserialize<'de>,
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserialize_bounded_sequence(deserializer)
}
}
/// Deserialize a sequence directly into a bounded vector.
///
/// Sequence formats may provide a length through [`SeqAccess::size_hint`].
/// When that length exceeds `MAX`, it is rejected before any element is
/// decoded. Formats without a reliable hint are still bounded by stopping at
/// the first element beyond `MAX`.
pub fn deserialize_bounded_sequence<'de, T, const MIN: usize, const MAX: usize, D>(
deserializer: D,
) -> Result<BoundedVec<T, MIN, MAX>, D::Error>
where
T: Deserialize<'de>,
D: Deserializer<'de>,
{
deserializer.deserialize_seq(BoundedSequenceVisitor {
marker: PhantomData,
})
}
struct BoundedSequenceVisitor<T, const MIN: usize, const MAX: usize> {
marker: PhantomData<T>,
}
impl<'de, T, const MIN: usize, const MAX: usize> Visitor<'de>
for BoundedSequenceVisitor<T, MIN, MAX>
where
T: Deserialize<'de>,
{
type Value = BoundedVec<T, MIN, MAX>;
fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(formatter, "a sequence with between {MIN} and {MAX} items")
}
fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let size_hint = sequence.size_hint();
if let Some(size_hint) = size_hint.filter(|&size_hint| size_hint > MAX) {
return Err(A::Error::custom(BoundedError::TooManyItems {
count: size_hint,
max: MAX,
}));
}
let capacity = size_hint.unwrap_or(0).min(MAX);
let mut values = Vec::with_capacity(capacity);
while let Some(value) = sequence.next_element()? {
if values.len() == MAX {
return Err(A::Error::custom(BoundedError::TooManyItems {
count: MAX.saturating_add(1),
max: MAX,
}));
}
values.push(value);
}
BoundedVec::try_from(values).map_err(A::Error::custom)
}
}
impl<T, const MIN: usize, const MAX: usize> Bounded<Vec<T>, MIN, MAX> {
/// Constructs an empty vector.
///
/// This is only valid when `MIN` is zero; it panics if `MIN` is non-zero.
#[must_use]
pub const fn empty() -> Self {
const { assert!(MIN == 0, "Cannot construct empty BoundedVec when MIN > 0") }
Self::new_unchecked(Vec::new())
}
/// Constructs an empty vector with at least the specified capacity.
///
/// The `capacity` must be within the `[MIN, MAX]` bounds; returns
/// [`BoundedError::CapacityOutOfBounds`] when `capacity` is outside this
/// range.
///
/// This does **not** change the length of the vector (it is still zero
/// after construction), but pre-allocates space so that at least
/// `capacity` elements can be pushed without reallocation.
pub fn with_capacity(capacity: usize) -> Result<Self, BoundedError> {
if MIN > 0 || capacity > MAX {
return Err(BoundedError::CapacityOutOfBounds {
min: MIN,
max: MAX,
capacity,
});
}
Ok(Self::new_unchecked(Vec::with_capacity(capacity)))
}
/// Constructs an empty vector with exactly `CAPACITY` pre-allocated slots,
/// with the bounds enforced at **compile time**.
///
/// Panics at compile time when `CAPACITY > MAX` or when `MIN > 0` (since
/// the resulting vector would be immediately below the minimum bound).
#[must_use]
pub fn with_const_capacity<const CAPACITY: usize>() -> Self {
const {
assert!(MIN == 0, "Cannot construct empty BoundedVec when MIN > 0");
assert!(CAPACITY <= MAX, "Requested capacity exceeds BoundedVec MAX");
}
Self::new_unchecked(Vec::with_capacity(CAPACITY))
}
/// Returns the number of elements in the vector.
#[must_use]
pub const fn len(&self) -> usize {
self.as_inner().len()
}
/// Returns `true` if the vector contains no elements.
#[must_use]
pub const fn is_empty(&self) -> bool {
self.as_inner().is_empty()
}
/// Returns the first element, or `None` if the vector is empty.
#[must_use]
// TODO: This function should not return an `Option` when `MIN >= 1`, but at the
// moment this is not possible in the current Rust version.
pub fn first(&self) -> Option<&T> {
self.as_inner().first()
}
/// Returns an iterator over the elements of the vector.
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.as_inner().iter()
}
/// Returns the elements as a slice.
#[must_use]
pub fn as_slice(&self) -> &[T] {
self.as_inner()
}
/// Appends an element if doing so does not exceed `MAX`.
///
/// Returns [`BoundedError::TooManyItems`] when the vector is already at
/// its maximum length.
pub fn try_push(&mut self, item: T) -> Result<(), BoundedError> {
if self.len() >= MAX {
return Err(BoundedError::TooManyItems {
count: self.len() + 1,
max: MAX,
});
}
self.0.push(item);
Ok(())
}
/// Removes and returns the last element if the minimum length is kept.
///
/// Returns `Ok(None)` when the vector is empty or already at its minimum
/// length.
pub fn try_pop(&mut self) -> Result<Option<T>, BoundedError> {
if self.is_empty() || self.len() - 1 < MIN {
return Ok(None);
}
self.try_remove(self.len() - 1).map(Some)
}
/// Removes and returns the element at `index` if the minimum length is
/// kept.
///
/// Returns [`BoundedError::IndexOutOfBounds`] for an invalid index and
/// [`BoundedError::TooFewItems`] when removing the element would violate
/// `MIN`.
pub fn try_remove(&mut self, index: usize) -> Result<T, BoundedError> {
// This check also guards against an empty vec, `index >= 0` and `self.len() =
// 0` will return and error
if index >= self.len() {
return Err(BoundedError::IndexOutOfBounds {
index,
len: self.len(),
});
}
let new_len = self.len() - 1;
if new_len < MIN {
return Err(BoundedError::TooFewItems {
count: new_len,
min: MIN,
});
}
Ok(self.0.remove(index))
}
/// Returns a mutable iterator over the elements of the vector.
pub fn iter_mut(&mut self) -> IterMut<'_, T> {
self.0.iter_mut()
}
/// Constructs a bounded vector from an iterable of elements.
///
/// Returns an error if the iterable contains fewer than `MIN` or more than
/// `MAX` elements. Iteration stops as soon as the maximum is exceeded.
pub fn try_from_iter<I>(iterable: I) -> Result<Self, BoundedError>
where
I: IntoIterator<Item = T>,
{
let mut values = Vec::new();
for value in iterable {
if values.len() == MAX {
return Err(BoundedError::TooManyItems {
count: MAX + 1,
max: MAX,
});
}
values.push(value);
}
Self::try_from(values)
}
/// Filters and maps elements while preserving the upper length bound.
///
/// Filtering may reduce the collection below `MIN`, so the result has
/// only an upper bound.
#[must_use]
pub fn filter_map_ref<U, F>(&self, f: F) -> UpperBoundedVec<U, MAX>
where
F: FnMut(&T) -> Option<U>,
{
Bounded::new_unchecked(self.as_inner().iter().filter_map(f).collect())
}
/// Maps borrowed elements while preserving both length bounds.
#[must_use]
pub fn map_ref<U, F>(&self, f: F) -> BoundedVec<U, MIN, MAX>
where
F: FnMut(&T) -> U,
{
Bounded::new_unchecked(self.as_inner().iter().map(f).collect())
}
}
impl<T, const MIN: usize, const MAX: usize> Default for Bounded<Vec<T>, MIN, MAX> {
fn default() -> Self {
const {
assert!(
MIN == 0,
"Default is only valid for BoundedVec with MIN == 0"
);
}
Self::new_unchecked(Vec::new())
}
}
impl<T, const MIN: usize, const MAX: usize> TryFrom<Vec<T>> for Bounded<Vec<T>, MIN, MAX> {
type Error = BoundedError;
fn try_from(value: Vec<T>) -> Result<Self, Self::Error> {
Self::try_new(value)
}
}
impl<T, const MIN: usize, const MAX: usize> TryFrom<&[T]> for Bounded<Vec<T>, MIN, MAX>
where
T: Clone,
{
type Error = BoundedError;
fn try_from(value: &[T]) -> Result<Self, Self::Error> {
Self::try_from(value.to_vec())
}
}
impl<T, const MIN: usize, const MAX: usize> From<T> for Bounded<Vec<T>, MIN, MAX> {
fn from(value: T) -> Self {
const {
assert!(
MIN <= 1,
"Single-element construction is invalid for minimum bound > 1"
);
assert!(
MAX >= 1,
"Single-element construction is invalid for maximum bound < 1"
);
}
Self::new_unchecked([value].into())
}
}
impl<T, const MIN: usize, const MAX: usize, const INPUT_SIZE: usize> From<[T; INPUT_SIZE]>
for Bounded<Vec<T>, MIN, MAX>
{
fn from(value: [T; INPUT_SIZE]) -> Self {
const {
assert!(INPUT_SIZE >= MIN, "Array length is below BoundedVec MIN");
assert!(INPUT_SIZE <= MAX, "Array length exceeds BoundedVec MAX");
}
Self::new_unchecked(value.into())
}
}
impl<const MAX: usize> FromStr for Bounded<Vec<u8>, 0, MAX> {
type Err = BoundedError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s.as_bytes().to_vec())
}
}
impl<T, const MIN: usize, const MAX: usize, const INPUT_SIZE: usize> From<&[T; INPUT_SIZE]>
for Bounded<Vec<T>, MIN, MAX>
where
T: Clone,
{
fn from(value: &[T; INPUT_SIZE]) -> Self {
value.clone().into()
}
}
impl<T, const MIN: usize, const MAX: usize> From<Bounded<Self, MIN, MAX>> for Vec<T> {
fn from(value: Bounded<Self, MIN, MAX>) -> Self {
value.into_inner()
}
}
impl<T, const MIN: usize, const MAX: usize> AsRef<[T]> for Bounded<Vec<T>, MIN, MAX> {
fn as_ref(&self) -> &[T] {
self.as_inner()
}
}
impl<T, const MIN: usize, const MAX: usize> Deref for Bounded<Vec<T>, MIN, MAX> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.as_inner()
}
}
impl<T, const MIN: usize, const MAX: usize> DerefMut for Bounded<Vec<T>, MIN, MAX> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<'a, T, const MIN: usize, const MAX: usize> IntoIterator for &'a mut Bounded<Vec<T>, MIN, MAX> {
type Item = &'a mut T;
type IntoIter = IterMut<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter_mut()
}
}
impl<'a, T, const MIN: usize, const MAX: usize> IntoIterator for &'a Bounded<Vec<T>, MIN, MAX> {
type Item = &'a T;
type IntoIter = Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.as_inner().iter()
}
}
impl<T, const MIN: usize, const MAX: usize> IntoIterator for Bounded<Vec<T>, MIN, MAX> {
type Item = T;
type IntoIter = IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.into_inner().into_iter()
}
}
/// A bounded vector containing between zero and `MAX` elements.
pub type UpperBoundedVec<T, const MAX: usize> = BoundedVec<T, 0, MAX>;
/// A bounded vector containing at least `MIN` elements.
pub type LowerBoundedVec<T, const MIN: usize> = BoundedVec<T, MIN, { usize::MAX }>;
/// A non-empty bounded vector containing at most `MAX` elements.
pub type NonEmptyBoundedVec<T, const MAX: usize> = BoundedVec<T, 1, MAX>;
/// A vector with no practical length bound.
pub type MaxBoundedVec<T> = UpperBoundedVec<T, { usize::MAX }>;
#[cfg(test)]
mod tests {
use std::sync::{
Mutex,
atomic::{AtomicUsize, Ordering},
};
use serde::{Deserialize, Deserializer};
use crate::bounded::{BoundedError, BoundedVec, UpperBoundedVec};
/// Concrete instantiation used across the tests: between 2 and 4 elements.
type TestBoundedVectorMin2 = BoundedVec<u8, 2, 4>;
type TestBoundedVectorMin1 = BoundedVec<u8, 1, 4>;
type TestBoundedVectorMin0 = BoundedVec<u8, 0, 4>;
static ELEMENT_ATTEMPTS: AtomicUsize = AtomicUsize::new(0);
static ELEMENT_ATTEMPTS_TEST_LOCK: Mutex<()> = Mutex::new(());
struct CountingByte;
impl<'de> Deserialize<'de> for CountingByte {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
ELEMENT_ATTEMPTS.fetch_add(1, Ordering::Relaxed);
u8::deserialize(deserializer).map(|_| Self)
}
}
#[test]
fn from_accepts_single_element_construction() {
let single = TestBoundedVectorMin0::from(1);
assert_eq!(single.as_slice(), &[1]);
let single = TestBoundedVectorMin1::from(1);
assert_eq!(single.as_slice(), &[1]);
/*
This does not compile:
```
let single = TestBoundedVectorMin2::from(1);
```
*/
}
#[test]
fn min_max_constants_reflect_the_generic_parameters() {
assert_eq!(TestBoundedVectorMin2::MIN, 2);
assert_eq!(TestBoundedVectorMin2::MAX, 4);
}
#[test]
fn new_unchecked_wraps_without_validation() {
// `new_unchecked` deliberately bypasses the bounds, so it accepts
// inputs that `try_from` would reject.
let empty = TestBoundedVectorMin2::new_unchecked(vec![]);
assert!(empty.is_empty());
let too_long = TestBoundedVectorMin2::new_unchecked(vec![1, 2, 3, 4, 5]);
assert_eq!(too_long.len(), 5);
}
#[test]
fn len_and_is_empty() {
let bv = TestBoundedVectorMin2::try_from(vec![1, 2, 3]).unwrap();
assert_eq!(bv.len(), 3);
assert!(!bv.is_empty());
assert!(TestBoundedVectorMin2::new_unchecked(vec![]).is_empty());
assert_eq!(TestBoundedVectorMin2::new_unchecked(vec![]).len(), 0);
}
#[test]
fn first_returns_the_leading_element() {
let bv = TestBoundedVectorMin2::try_from(vec![10, 20, 30]).unwrap();
assert_eq!(bv.first(), Some(&10));
assert_eq!(TestBoundedVectorMin2::new_unchecked(vec![]).first(), None);
}
#[test]
fn iter_yields_every_element_in_order() {
let bv = TestBoundedVectorMin2::try_from(vec![1, 2, 3]).unwrap();
assert_eq!(bv.iter().copied().collect::<Vec<_>>(), vec![1, 2, 3]);
}
#[test]
fn into_inner_returns_the_backing_vec() {
let bv = TestBoundedVectorMin2::try_from(vec![7, 8]).unwrap();
assert_eq!(bv.into_inner(), vec![7, 8]);
}
#[test]
fn as_slice_exposes_the_contents() {
let bv = TestBoundedVectorMin2::try_from(vec![4, 5, 6]).unwrap();
assert_eq!(bv.as_slice(), &[4, 5, 6]);
}
#[test]
fn try_push_appends_while_under_the_cap() {
let mut bv = TestBoundedVectorMin2::try_from(vec![1, 2]).unwrap();
assert_eq!(bv.try_push(3), Ok(()));
assert_eq!(bv.try_push(4), Ok(()));
assert_eq!(bv.as_slice(), &[1, 2, 3, 4]);
}
#[test]
fn try_push_rejects_growth_past_max() {
let mut bv = TestBoundedVectorMin2::try_from(vec![1, 2, 3, 4]).unwrap();
assert_eq!(
bv.try_push(5),
Err(BoundedError::TooManyItems { count: 5, max: 4 })
);
// The failed push must not have mutated the vector.
assert_eq!(bv.as_slice(), &[1, 2, 3, 4]);
}
#[test]
fn try_from_accepts_lengths_within_bounds() {
TestBoundedVectorMin0::try_from(vec![]).unwrap();
TestBoundedVectorMin0::try_from(vec![1]).unwrap();
TestBoundedVectorMin0::try_from(vec![1, 2]).unwrap();
TestBoundedVectorMin0::try_from(vec![1, 2, 3]).unwrap();
TestBoundedVectorMin0::try_from(vec![1, 2, 3, 4]).unwrap();
TestBoundedVectorMin2::try_from(vec![1, 2]).unwrap();
TestBoundedVectorMin2::try_from(vec![1, 2, 3]).unwrap();
TestBoundedVectorMin2::try_from(vec![1, 2, 3, 4]).unwrap();
}
#[test]
fn try_from_rejects_input_below_min() {
assert_eq!(
TestBoundedVectorMin2::try_from(vec![]),
Err(BoundedError::EmptyInput)
);
assert_eq!(
TestBoundedVectorMin2::try_from(vec![1]),
Err(BoundedError::TooFewItems { count: 1, min: 2 })
);
}
#[test]
fn try_from_rejects_input_above_max() {
assert_eq!(
TestBoundedVectorMin2::try_from(vec![1, 2, 3, 4, 5]),
Err(BoundedError::TooManyItems { count: 5, max: 4 })
);
assert_eq!(
TestBoundedVectorMin0::try_from(vec![1, 2, 3, 4, 5]),
Err(BoundedError::TooManyItems { count: 5, max: 4 })
);
}
#[test]
fn from_single_value_builds_a_one_element_vec() {
// `From<T>` requires `MIN <= 1`; `MAX` here comfortably allows one.
let bv: BoundedVec<u8, 1, 4> = 42.into();
assert_eq!(bv.as_slice(), &[42]);
}
#[test]
fn from_owned_array() {
let bv: TestBoundedVectorMin2 = [1, 2, 3].into();
assert_eq!(bv.as_slice(), &[1, 2, 3]);
}
#[test]
fn from_array_reference() {
let bv: TestBoundedVectorMin2 = (&[9, 8, 7]).into();
assert_eq!(bv.as_slice(), &[9, 8, 7]);
}
#[test]
fn into_vec_unwraps_the_bounded_vec() {
let bv = TestBoundedVectorMin2::try_from(vec![1, 2, 3]).unwrap();
let raw: Vec<u8> = bv.into();
assert_eq!(raw, vec![1, 2, 3]);
}
#[test]
fn as_ref_slice_and_vec() {
let bv = TestBoundedVectorMin2::try_from(vec![1, 2, 3]).unwrap();
let slice: &[u8] = bv.as_ref();
assert_eq!(slice, &[1, 2, 3]);
let vec: &Vec<u8> = bv.as_ref();
assert_eq!(vec, &vec![1, 2, 3]);
}
#[test]
fn into_iterator_by_reference() {
let bv = TestBoundedVectorMin2::try_from(vec![1, 2, 3]).unwrap();
let collected: Vec<u8> = (&bv).into_iter().copied().collect();
assert_eq!(collected, vec![1, 2, 3]);
// `bv` is still usable after iterating by reference.
assert_eq!(bv.len(), 3);
}
#[test]
fn into_iterator_by_value() {
let bv = TestBoundedVectorMin2::try_from(vec![1, 2, 3]).unwrap();
let collected: Vec<u8> = bv.into_iter().collect();
assert_eq!(collected, vec![1, 2, 3]);
}
#[test]
fn equality_and_ordering() {
let a = TestBoundedVectorMin2::try_from(vec![1, 2]).unwrap();
let b = TestBoundedVectorMin2::try_from(vec![1, 2]).unwrap();
let c = TestBoundedVectorMin2::try_from(vec![1, 3]).unwrap();
assert_eq!(a, b);
assert!(a < c);
}
#[test]
fn serialize_emits_a_plain_sequence() {
let bv = TestBoundedVectorMin2::try_from(vec![1, 2, 3]).unwrap();
assert_eq!(serde_json::to_string(&bv).unwrap(), "[1,2,3]");
}
#[test]
fn deserialize_accepts_input_within_bounds() {
let bv: TestBoundedVectorMin2 = serde_json::from_str("[1,2,3]").unwrap();
assert_eq!(bv.as_slice(), &[1, 2, 3]);
}
#[test]
fn deserialize_accepts_inputs_at_bounds() {
let min: TestBoundedVectorMin2 = serde_json::from_str("[1,2]").unwrap();
assert_eq!(min.as_slice(), &[1, 2]);
let max: TestBoundedVectorMin2 = serde_json::from_str("[1,2,3,4]").unwrap();
assert_eq!(max.as_slice(), &[1, 2, 3, 4]);
}
#[test]
fn serialize_then_deserialize_roundtrips() {
let original = TestBoundedVectorMin2::try_from(vec![5, 6, 7, 8]).unwrap();
let json = serde_json::to_string(&original).unwrap();
let restored: TestBoundedVectorMin2 = serde_json::from_str(&json).unwrap();
assert_eq!(original, restored);
}
#[test]
fn deserialize_rejects_input_below_min() {
let err = serde_json::from_str::<TestBoundedVectorMin2>("[1]").unwrap_err();
assert!(
err.to_string()
.contains("Item count 1 is below minimum of 2"),
"unexpected error: {err}"
);
}
#[test]
fn deserialize_rejects_empty_input() {
let err = serde_json::from_str::<TestBoundedVectorMin2>("[]").unwrap_err();
assert!(
err.to_string().contains("Input cannot be empty"),
"unexpected error: {err}"
);
}
#[test]
fn deserialize_rejects_input_above_max() {
let err = serde_json::from_str::<TestBoundedVectorMin2>("[1,2,3,4,5]").unwrap_err();
assert!(
err.to_string().contains("exceeds static maximum"),
"unexpected error: {err}"
);
}
#[test]
fn deserialize_json_stops_after_at_most_one_element_past_maximum() {
let _test_guard = ELEMENT_ATTEMPTS_TEST_LOCK.lock().unwrap();
ELEMENT_ATTEMPTS.store(0, Ordering::Relaxed);
let result = serde_json::from_str::<BoundedVec<CountingByte, 0, 4>>("[1,2,3,4,5,6]");
assert!(result.is_err());
assert!(ELEMENT_ATTEMPTS.load(Ordering::Relaxed) <= 5);
}
#[test]
fn deserialize_binary_rejects_oversized_length_before_decoding_elements() {
let _test_guard = ELEMENT_ATTEMPTS_TEST_LOCK.lock().unwrap();
ELEMENT_ATTEMPTS.store(0, Ordering::Relaxed);
let encoded = bincode::serialize(&vec![1u8; 5]).unwrap();
let result = bincode::deserialize::<BoundedVec<CountingByte, 0, 4>>(&encoded);
assert!(result.is_err());
assert_eq!(ELEMENT_ATTEMPTS.load(Ordering::Relaxed), 0);
}
#[test]
fn deserialize_binary_preserves_the_vector_wire_format() {
let original = TestBoundedVectorMin2::try_from(vec![5, 6, 7]).unwrap();
let encoded = bincode::serialize(&original).unwrap();
let restored = bincode::deserialize::<TestBoundedVectorMin2>(&encoded).unwrap();
assert_eq!(restored, original);
assert_eq!(encoded, bincode::serialize(&vec![5u8, 6, 7]).unwrap());
}
#[test]
fn try_pop_returns_none_at_or_below_lower_bound_and_is_idempotent() {
let mut bv = TestBoundedVectorMin2::try_from(vec![1, 2, 3]).unwrap();
assert_eq!(bv.try_pop(), Ok(Some(3)));
assert_eq!(bv.try_pop(), Ok(None));
assert_eq!(bv.try_pop(), Ok(None));
assert_eq!(bv.as_slice(), &[1, 2]);
assert_eq!(bv.len(), 2);
let mut bv = TestBoundedVectorMin0::try_from(vec![1, 2, 3]).unwrap();
assert_eq!(bv.try_pop(), Ok(Some(3)));
assert_eq!(bv.try_pop(), Ok(Some(2)));
assert_eq!(bv.try_pop(), Ok(Some(1)));
assert_eq!(bv.try_pop(), Ok(None));
assert_eq!(bv.try_pop(), Ok(None));
assert!(bv.is_empty());
}
#[test]
fn try_remove_removes_item_at_index_when_above_min() {
let mut bv = TestBoundedVectorMin2::try_from(vec![1, 2, 3, 4]).unwrap();
assert_eq!(bv.try_remove(1), Ok(2));
assert_eq!(bv.as_slice(), &[1, 3, 4]);
assert_eq!(bv.len(), 3);
}
#[test]
fn try_remove_rejects_removal_below_min_and_does_not_mutate() {
let mut bv = TestBoundedVectorMin2::try_from(vec![1, 2]).unwrap();
assert_eq!(
bv.try_remove(0),
Err(BoundedError::TooFewItems { count: 1, min: 2 })
);
assert_eq!(bv.as_slice(), &[1, 2]);
assert_eq!(bv.len(), 2);
}
#[test]
fn try_remove_rejects_out_of_bounds_and_does_not_mutate() {
let mut bv = TestBoundedVectorMin2::try_from(vec![1, 2, 3]).unwrap();
assert_eq!(
bv.try_remove(3),
Err(BoundedError::IndexOutOfBounds { index: 3, len: 3 })
);
assert_eq!(bv.as_slice(), &[1, 2, 3]);
assert_eq!(bv.len(), 3);
}
#[test]
fn try_from_iter_accepts_items_within_bounds() {
let bounded = TestBoundedVectorMin0::try_from_iter([1, 2, 3]).unwrap();
assert_eq!(bounded.as_slice(), &[1, 2, 3]);
}
#[test]
fn try_from_iter_accepts_an_empty_iterator_when_minimum_is_zero() {
let bounded = TestBoundedVectorMin0::try_from_iter(std::iter::empty()).unwrap();
assert!(bounded.is_empty());
}
#[test]
fn try_from_iter_rejects_items_above_maximum() {
let result = TestBoundedVectorMin0::try_from_iter([1, 2, 3, 4, 5]);
assert_eq!(result, Err(BoundedError::TooManyItems { count: 5, max: 4 }));
}
#[test]
fn try_from_iter_rejects_items_below_minimum() {
let result = TestBoundedVectorMin2::try_from_iter([1]);
assert_eq!(result, Err(BoundedError::TooFewItems { count: 1, min: 2 }));
}
#[test]
fn try_from_iter_preserves_iterator_order() {
let input = (0..4).map(|value| value * 2);
let bounded = TestBoundedVectorMin0::try_from_iter(input).unwrap();
assert_eq!(bounded.as_slice(), &[0, 2, 4, 6]);
}
#[derive(Debug, PartialEq, Eq)]
struct MyNewType(u32);
#[test]
fn map_preserves_bounds_when_mapping_to_another_inner_type() {
type Source = BoundedVec<u16, 2, 4>;
type Mapped = BoundedVec<MyNewType, 2, 4>;
let source = Source::try_from(vec![10u16, 20, 30, 40]).unwrap();
// The explicit type annotation proves at compile time that map preserves
// the source bounds while changing the inner type.
let mapped: Mapped = source.map_ref(|&value| MyNewType(u32::from(value)));
assert_eq!(Mapped::MIN, Source::MIN);
assert_eq!(Mapped::MAX, Source::MAX);
assert_eq!(mapped.len(), 4);
assert_eq!(
mapped.as_slice(),
&[MyNewType(10), MyNewType(20), MyNewType(30), MyNewType(40),]
);
}
#[test]
fn filter_map_preserves_upper_bound_when_filtering_items() {
type Source1 = BoundedVec<u16, 2, 4>;
type Source2 = UpperBoundedVec<u16, 4>;
type Mapped = UpperBoundedVec<MyNewType, 4>;
let source_1 = Source1::try_from(vec![10u16, 20, 30, 40]).unwrap();
let source_2 = Source2::try_from(vec![10u16, 20, 30, 40]).unwrap();
// The explicit type annotation proves at compile time that filter_map
// preserves the upper bound while dropping the lower bound.
let mapped_1: Mapped =
source_1.filter_map_ref(|value| (*value > 20).then(|| MyNewType(u32::from(*value))));
assert_eq!(Mapped::MIN, 0);
assert_eq!(Mapped::MAX, Source1::MAX);
assert_eq!(mapped_1.len(), 2);
assert_eq!(mapped_1.as_slice(), &[MyNewType(30), MyNewType(40)]);
let mapped_2: Mapped =
source_2.filter_map_ref(|value| (*value > 20).then(|| MyNewType(u32::from(*value))));
assert_eq!(Mapped::MIN, 0);
assert_eq!(Mapped::MAX, Source1::MAX);
assert_eq!(mapped_2.len(), 2);
assert_eq!(mapped_2.as_slice(), &[MyNewType(30), MyNewType(40)]);
}
#[test]
fn map_ref_preserves_bounds_when_mapping_to_another_inner_type() {
type Source = BoundedVec<u16, 2, 4>;
type Mapped = BoundedVec<MyNewType, 2, 4>;
let source = Source::try_from(vec![10u16, 20, 30, 40]).unwrap();
// The explicit type annotation proves at compile time that map_ref
// preserves both the minimum and maximum bounds.
let mapped: Mapped = source.map_ref(|value| MyNewType(u32::from(*value)));
assert_eq!(Mapped::MIN, Source::MIN);
assert_eq!(Mapped::MAX, Source::MAX);
assert_eq!(mapped.len(), source.len());
assert_eq!(
mapped.as_slice(),
&[MyNewType(10), MyNewType(20), MyNewType(30), MyNewType(40),]
);
}
#[test]
fn with_const_capacity_succeeds() {
type V = BoundedVec<u32, 0, 10>;
let v = V::with_const_capacity::<5>();
assert_eq!(v.len(), 0);
assert!(v.as_inner().capacity() >= 5);
}
#[test]
fn with_capacity_within_bounds_succeeds() {
type V = BoundedVec<u32, 0, 10>;
let v = V::with_capacity(5).unwrap();
assert_eq!(v.len(), 0);
assert!(v.as_inner().capacity() >= 5);
}
#[test]
fn with_capacity_at_max_succeeds() {
type V = BoundedVec<u32, 0, 4>;
let v = V::with_capacity(4).unwrap();
assert_eq!(v.len(), 0);
assert!(v.as_inner().capacity() >= 4);
}
#[test]
fn with_capacity_above_max_returns_error() {
type V = BoundedVec<u32, 0, 4>;
assert!(matches!(
V::with_capacity(5),
Err(BoundedError::CapacityOutOfBounds {
min: 0,
max: 4,
capacity: 5
})
));
}
#[test]
fn with_capacity_rejects_nonzero_min() {
type V = BoundedVec<u32, 2, 10>;
assert!(matches!(
V::with_capacity(5),
Err(BoundedError::CapacityOutOfBounds {
min: 2,
max: 10,
capacity: 5
})
));
}
}