Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nebz"
version = "0.1.2"
version = "0.2.0"
edition = "2024"
description = "an immutable non-empty bytes container"
readme = "README.md"
Expand Down
63 changes: 43 additions & 20 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@

#![cfg_attr(docsrs, feature(doc_auto_cfg))]

use core::num::NonZeroUsize;

/// A container to `T` that is known to be non-empty.
///
/// A `T: AsRef<[u8]>` is empty if `AsRef::<[u8]>::as_ref(&T).is_empty()` is `true`.
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct NonEmptyBz<T>(T);

impl<T> NonEmptyBz<T> {
/// Creates a [`NonEmptyBz`] if the given `bz` is not empty.
/// Creates a `NonEmptyBz<T>` if the given `bz` is not empty.
///
/// If 'bz' is `[u8; N]` or `&[u8; N]`, consider using `NonEmptyBz::from_#_array()` constructors.
/// If `bz` is `[u8; N]` or `&[u8; N]`, consider using `NonEmptyBz::from_#_array()` constructor.
pub fn new(bz: T) -> Option<Self>
where
T: AsRef<[u8]>,
Expand Down Expand Up @@ -91,31 +93,27 @@ impl<T> NonEmptyBz<T> {
NonEmptyBz(&self.0)
}

/// Returns [`NonEmptyBz`] containing the [`AsRef::<[u8]>::as_ref`] of the inner value.
/// Returns [`NonEmptyBz`] containing the `AsRef::<[u8]>::as_ref` of the inner value.
pub fn as_ref_slice(&self) -> NonEmptyBz<&[u8]>
where
T: AsRef<[u8]>,
{
NonEmptyBz(self.0.as_ref())
}

/// Returns the `len` of the inner value's [`AsRef::<[u8]>::as_ref`].
pub fn len(&self) -> usize
where
T: AsRef<[u8]>,
{
self.0.as_ref().len()
}

/// Returns true if the `len` of the inner value's [`AsRef::<[u8]>::as_ref`] is 0.
pub fn is_empty(&self) -> bool
/// Returns the `len` of the inner value's `AsRef::<[u8]>::as_ref`.
pub fn len(&self) -> NonZeroUsize
where
T: AsRef<[u8]>,
{
self.0.as_ref().is_empty()
// SAFETY: `NonEmptyBz` guarantees that the contained value is non-empty,
// i.e. `self.0.as_ref().len() >= 1`. This invariant is maintained by:
// - The `NonEmptyBz::new()` constructor which rejects empty values
// - The array constructors which use compile-time assertions to ensure non-empty array
unsafe { NonZeroUsize::new_unchecked(self.0.as_ref().len()) }
}

/// Unwraps the value, consuming the [`NonEmptyBz`].
/// Unwraps the value, consuming the `NonEmptyBz`.
pub fn into_inner(self) -> T {
self.0
}
Expand All @@ -132,19 +130,19 @@ impl<T> NonEmptyBz<&T> {
}

impl<const N: usize> NonEmptyBz<[u8; N]> {
/// Creates a [`NonEmptyBz`] containing the array `bz`.
/// Creates a `NonEmptyBz<[u8; N]>` containing the array `bz`.
///
/// Will cause compile-time error if `N` is 0.
/// Will cause compile-time error if `N == 0`.
pub const fn from_owned_array(bz: [u8; N]) -> Self {
const { assert!(N != 0) }
Self(bz)
}
}

impl<'a, const N: usize> NonEmptyBz<&'a [u8; N]> {
/// Creates a [`NonEmptyBz`] containing the borrowed array `bz`.
/// Creates a `NonEmptyBz<&[u8; N]>` containing the borrowed array `bz`.
///
/// Will cause compile-time error if `N` is 0.
/// Will cause compile-time error if `N == 0`.
pub const fn from_borrowed_array(bz: &'a [u8; N]) -> Self {
const { assert!(N != 0) }
Self(bz)
Expand All @@ -153,7 +151,8 @@ impl<'a, const N: usize> NonEmptyBz<&'a [u8; N]> {

#[cfg(feature = "bytes")]
impl From<NonEmptyBz<&[u8]>> for NonEmptyBz<bytes::Bytes> {
/// Creates a [`NonEmptyBz`] containing `Bytes` from a [`NonEmptyBz`] containing `&[u8]` by invoking [`Bytes::copy_from_slice`](bytes::Bytes::copy_from_slice).
/// Creates a `NonEmptyBz<Bytes>` from a `NonEmptyBz<Bytes>` containing `&[u8]`
/// by invoking [`Bytes::copy_from_slice`](bytes::Bytes::copy_from_slice).
fn from(nebz_slice: NonEmptyBz<&[u8]>) -> Self {
Self(bytes::Bytes::copy_from_slice(nebz_slice.get()))
}
Expand Down Expand Up @@ -220,4 +219,28 @@ mod tests {
assert_eq!(last, expected_last);
assert_eq!(rest, expected_split_last_rest);
}

#[rstest]
// single byte
#[case::single_byte_owned_array(NonEmptyBz::from_owned_array([42]), NonZeroUsize::MIN)]
#[case::single_byte_borrowed_array(NonEmptyBz::from_borrowed_array(b"a"), NonZeroUsize::MIN)]
#[case::single_byte_vec(NonEmptyBz::new(vec![0]).unwrap(), NonZeroUsize::MIN)]
#[case::single_byte_str(NonEmptyBz::new("z").unwrap(), NonZeroUsize::MIN)]
// multiple bytes
#[case::multiple_bytes_owned_array(
NonEmptyBz::from_owned_array([1, 2, 3, 4]),
4.try_into().unwrap(),
)]
#[case::multiple_bytes_borrowed_array(
NonEmptyBz::from_borrowed_array(b"nebz"),
4.try_into().unwrap(),
)]
#[case::multiple_bytes_vec(NonEmptyBz::new(vec![255, 0, 128]).unwrap(), 3.try_into().unwrap())]
#[case::multiple_bytes_str(NonEmptyBz::new("hello").unwrap(), 5.try_into().unwrap())]
fn len_works<T>(#[case] nebz: NonEmptyBz<T>, #[case] expected_len: NonZeroUsize)
where
T: AsRef<[u8]>,
{
assert_eq!(nebz.len(), expected_len);
}
}