#[must_use]
pub fn present<T>(members: Vec<T>) -> Option<Vec<T>> {
(!members.is_empty()).then_some(members)
}
#[must_use]
pub fn present_nonempty<T>(members: Vec<T>) -> Option<NonEmptyVec<T>> {
NonEmptyVec::new(members).ok()
}
#[cfg(test)]
mod tests {
use super::present;
#[test]
fn an_empty_member_list_is_absence() {
assert_eq!(present(Vec::<u8>::new()), None);
}
#[test]
fn a_populated_member_list_is_presence() {
assert_eq!(present(vec![1_u8, 2]), Some(vec![1, 2]));
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NonEmptyVec<T>(Vec<T>);
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[error("a container with a cardinality lower bound of 1 must have at least one member")]
pub struct EmptyContainer;
impl<T> NonEmptyVec<T> {
pub fn new(members: Vec<T>) -> Result<Self, EmptyContainer> {
if members.is_empty() {
return Err(EmptyContainer);
}
Ok(Self(members))
}
#[must_use]
pub fn of(member: T) -> Self {
Self(vec![member])
}
pub fn push(&mut self, member: T) {
self.0.push(member);
}
#[must_use]
pub fn into_vec(self) -> Vec<T> {
self.0
}
#[must_use]
pub fn head(&self) -> &T {
#[expect(
clippy::indexing_slicing,
reason = "index 0 is in bounds by the type's construction invariant: `new` rejects an empty Vec and no method can shrink one"
)]
&self.0[0]
}
}
impl<T> TryFrom<Vec<T>> for NonEmptyVec<T> {
type Error = EmptyContainer;
fn try_from(members: Vec<T>) -> Result<Self, Self::Error> {
Self::new(members)
}
}
impl<T> From<NonEmptyVec<T>> for Vec<T> {
fn from(container: NonEmptyVec<T>) -> Self {
container.0
}
}
impl<T> core::ops::Deref for NonEmptyVec<T> {
type Target = [T];
fn deref(&self) -> &[T] {
&self.0
}
}
impl<T> core::ops::DerefMut for NonEmptyVec<T> {
fn deref_mut(&mut self) -> &mut [T] {
&mut self.0
}
}
impl<T> AsRef<[T]> for NonEmptyVec<T> {
fn as_ref(&self) -> &[T] {
&self.0
}
}
impl<T> IntoIterator for NonEmptyVec<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a, T> IntoIterator for &'a NonEmptyVec<T> {
type Item = &'a T;
type IntoIter = std::slice::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'a, T> IntoIterator for &'a mut NonEmptyVec<T> {
type Item = &'a mut T;
type IntoIter = std::slice::IterMut<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter_mut()
}
}
impl<T: serde::Serialize> serde::Serialize for NonEmptyVec<T> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for NonEmptyVec<T> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let members = Vec::<T>::deserialize(deserializer)?;
Self::new(members).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod nonempty_tests {
use super::{EmptyContainer, NonEmptyVec};
#[test]
fn an_empty_member_list_is_refused() {
assert_eq!(NonEmptyVec::<u8>::new(Vec::new()), Err(EmptyContainer));
}
#[test]
fn a_populated_member_list_is_accepted_and_reads_as_a_slice() {
let c = NonEmptyVec::new(vec![1_u8, 2]).expect("two members");
assert_eq!(c.len(), 2);
assert_eq!(c.head(), &1);
assert_eq!(c.iter().copied().collect::<Vec<_>>(), vec![1, 2]);
}
#[test]
fn growing_keeps_it_non_empty() {
let mut c = NonEmptyVec::of(1_u8);
c.push(2);
assert_eq!(c.into_vec(), vec![1, 2]);
}
}