//! Non-empty [`Range`] type that guarantees at least one element. use bytes::{Buf, BufMut}; use commonware_codec::{EncodeSize, Error as CodecError, Read, Write}; use core::{fmt, ops::Range}; /// Error returned when attempting to create a non-empty range from an empty range. #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] #[error("range is empty")] pub struct EmptyRange; /// A non-empty [`Range`] (`start..end`) where `start < end` is guaranteed. #[derive(Clone, PartialEq, Eq, Hash)] pub struct NonEmptyRange(Range); impl fmt::Debug for NonEmptyRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl NonEmptyRange { /// Creates a `NonEmptyRange` if `start < end`. pub fn new(range: Range) -> Result { (range.start < range.end) .then_some(Self(range)) .ok_or(EmptyRange) } } impl NonEmptyRange { /// Returns the start of the range. pub const fn start(&self) -> Idx { self.0.start } /// Returns the end of the range (exclusive). pub const fn end(&self) -> Idx { self.0.end } } impl TryFrom> for NonEmptyRange { type Error = EmptyRange; fn try_from(range: Range) -> Result { Self::new(range) } } impl From> for Range { fn from(r: NonEmptyRange) -> Self { r.0 } } impl IntoIterator for NonEmptyRange where Range: Iterator, { type Item = as Iterator>::Item; type IntoIter = Range; fn into_iter(self) -> Self::IntoIter { self.0 } } impl Write for NonEmptyRange { #[inline] fn write(&self, buf: &mut impl BufMut) { self.0.write(buf); } } impl EncodeSize for NonEmptyRange { #[inline] fn encode_size(&self) -> usize { self.0.encode_size() } } impl Read for NonEmptyRange { type Cfg = Idx::Cfg; #[inline] fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result { let range = Range::::read_cfg(buf, cfg)?; if !range .start .partial_cmp(&range.end) .is_some_and(|o| o.is_lt()) { return Err(CodecError::Invalid("NonEmptyRange", "start must be < end")); } Ok(Self(range)) } } #[cfg(feature = "arbitrary")] impl<'a, Idx: arbitrary::Arbitrary<'a> + Ord> arbitrary::Arbitrary<'a> for NonEmptyRange { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let a = Idx::arbitrary(u)?; let b = Idx::arbitrary(u)?; let (start, end) = if a < b { (a, b) } else if b < a { (b, a) } else { return Err(arbitrary::Error::IncorrectFormat); }; Ok(Self(start..end)) } } /// A macro to create a [`NonEmptyRange`] from a range expression, panicking if the range is empty. #[macro_export] macro_rules! non_empty_range { ($start:expr, $end:expr) => { $crate::range::NonEmptyRange::new($start..$end).expect("range must be non-empty") }; } #[cfg(test)] mod tests { use super::*; use commonware_codec::{DecodeExt, Encode}; #[test] fn test_non_empty_range_valid() { let r = NonEmptyRange::new(0u32..5).unwrap(); assert_eq!(r.start(), 0); assert_eq!(r.end(), 5); assert_eq!(Range::from(r), 0..5); } #[test] fn test_non_empty_range_single_element() { let r = NonEmptyRange::new(3u32..4).unwrap(); assert_eq!(r.start(), 3); assert_eq!(r.end(), 4); } #[test] fn test_non_empty_range_empty() { assert_eq!(NonEmptyRange::new(5u32..5), Err(EmptyRange)); #[allow(clippy::reversed_empty_ranges)] let reversed = NonEmptyRange::new(5u32..3); assert_eq!(reversed, Err(EmptyRange)); } #[test] fn test_non_empty_range_into() { let r = NonEmptyRange::new(1u32..10).unwrap(); let range: Range = r.into(); assert_eq!(range, 1..10); } #[test] fn test_non_empty_range_debug() { let r = NonEmptyRange::new(1u32..5).unwrap(); assert_eq!(format!("{r:?}"), "1..5"); } #[test] fn test_non_empty_range_iter() { let r = NonEmptyRange::new(0u32..4).unwrap(); let items: Vec<_> = r.into_iter().collect(); assert_eq!(items, vec![0, 1, 2, 3]); } #[test] fn test_non_empty_range_encode_decode() { let r = NonEmptyRange::new(10u32..20).unwrap(); let encoded = r.encode(); let decoded = NonEmptyRange::::decode(encoded).unwrap(); assert_eq!(r, decoded); } #[test] fn test_non_empty_range_decode_invalid() { // Manually encode start=20, end=10 to bypass the Range write panic let mut buf = Vec::new(); buf.extend_from_slice(&20u32.to_be_bytes()); buf.extend_from_slice(&10u32.to_be_bytes()); assert!(NonEmptyRange::::decode(bytes::Bytes::from(buf)).is_err()); // start == end is valid for Range but not for NonEmptyRange let empty = Range { start: 5u32, end: 5u32, }; let encoded = empty.encode(); assert!(NonEmptyRange::::decode(encoded).is_err()); } #[cfg(feature = "arbitrary")] mod conformance { use super::*; use commonware_codec::conformance::CodecConformance; commonware_conformance::conformance_tests! { CodecConformance>, CodecConformance>, } } }