//! Types for use as [crate::Read::Cfg]. use core::{ num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}, ops::{Bound, RangeBounds}, }; /// Configuration for limiting the range of a value. /// /// This is often used to configure length limits for variable-length types or collections. /// /// # Examples /// /// ``` /// use commonware_codec::RangeCfg; /// /// // Limit lengths to 0..=1024 (type inferred as usize) /// let cfg = RangeCfg::new(0..=1024); /// assert!(cfg.contains(&500)); /// assert!(!cfg.contains(&2000)); /// /// // Allow any length >= 1 /// let cfg_min = RangeCfg::from(1..); /// assert!(cfg_min.contains(&1)); /// assert!(!cfg_min.contains(&0)); /// /// // Works with other integer types /// let cfg_u8: RangeCfg = RangeCfg::new(0u8..=255u8); /// assert!(cfg_u8.contains(&128)); /// /// let cfg_u32 = RangeCfg::new(0u32..1024u32); /// assert!(cfg_u32.contains(&500)); /// ``` #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub struct RangeCfg { /// The lower bound of the range. start: Bound, /// The upper bound of the range. end: Bound, } impl From> for RangeCfg { fn from(r: core::ops::Range) -> Self { Self::new(r) } } impl From> for RangeCfg { fn from(r: core::ops::RangeInclusive) -> Self { Self::new(r) } } impl From> for RangeCfg { fn from(r: core::ops::RangeFrom) -> Self { Self::new(r) } } impl From> for RangeCfg { fn from(r: core::ops::RangeTo) -> Self { Self::new(r) } } impl From> for RangeCfg { fn from(r: core::ops::RangeToInclusive) -> Self { Self::new(r) } } impl From for RangeCfg { fn from(_: core::ops::RangeFull) -> Self { Self::new(..) } } macro_rules! impl_from_nonzero { ($nz_ty:ty, $ty:ty) => { impl From> for RangeCfg<$ty> { fn from(value: RangeCfg<$nz_ty>) -> Self { let start = match value.start { Bound::Included(nz) => Bound::Included(nz.get()), Bound::Excluded(nz) => Bound::Excluded(nz.get()), Bound::Unbounded => Bound::Unbounded, }; let end = match value.end { Bound::Included(nz) => Bound::Included(nz.get()), Bound::Excluded(nz) => Bound::Excluded(nz.get()), Bound::Unbounded => Bound::Unbounded, }; RangeCfg { start, end } } } }; } impl_from_nonzero!(NonZeroUsize, usize); impl_from_nonzero!(NonZeroU8, u8); impl_from_nonzero!(NonZeroU16, u16); impl_from_nonzero!(NonZeroU32, u32); impl_from_nonzero!(NonZeroU64, u64); macro_rules! impl_from_nonzero_to_usize { ($from_ty:ty) => { impl From> for RangeCfg { fn from(value: RangeCfg<$from_ty>) -> Self { let start = match value.start { Bound::Included(v) => Bound::Included( usize::try_from(v.get()).expect("range start exceeds usize"), ), Bound::Excluded(v) => Bound::Excluded( usize::try_from(v.get()).expect("range start exceeds usize"), ), Bound::Unbounded => Bound::Unbounded, }; let end = match value.end { Bound::Included(v) => { Bound::Included(usize::try_from(v.get()).expect("range end exceeds usize")) } Bound::Excluded(v) => { Bound::Excluded(usize::try_from(v.get()).expect("range end exceeds usize")) } Bound::Unbounded => Bound::Unbounded, }; RangeCfg { start, end } } } }; } impl_from_nonzero_to_usize!(NonZeroU8); impl_from_nonzero_to_usize!(NonZeroU16); impl_from_nonzero_to_usize!(NonZeroU32); macro_rules! impl_nonzero_to_nonzero_usize { ($from_ty:ty) => { impl From> for RangeCfg { fn from(value: RangeCfg<$from_ty>) -> Self { let start = match value.start { Bound::Included(v) => Bound::Included( NonZeroUsize::try_from(v).expect("range start exceeds usize"), ), Bound::Excluded(v) => Bound::Excluded( NonZeroUsize::try_from(v).expect("range start exceeds usize"), ), Bound::Unbounded => Bound::Unbounded, }; let end = match value.end { Bound::Included(v) => { Bound::Included(NonZeroUsize::try_from(v).expect("range end exceeds usize")) } Bound::Excluded(v) => { Bound::Excluded(NonZeroUsize::try_from(v).expect("range end exceeds usize")) } Bound::Unbounded => Bound::Unbounded, }; RangeCfg { start, end } } } }; } impl_nonzero_to_nonzero_usize!(NonZeroU8); impl_nonzero_to_nonzero_usize!(NonZeroU16); impl_nonzero_to_nonzero_usize!(NonZeroU32); impl RangeCfg { /// Creates a new `RangeCfg` from any type implementing `RangeBounds`. /// /// # Examples /// /// ``` /// use commonware_codec::RangeCfg; /// /// let cfg = RangeCfg::new(0..=1024); /// assert!(cfg.contains(&500)); /// ``` pub fn new(r: impl RangeBounds) -> Self { Self { start: r.start_bound().cloned(), end: r.end_bound().cloned(), } } /// Creates a `RangeCfg` that only accepts exactly `value`. pub const fn exact(value: T) -> Self { Self { start: Bound::Included(value), end: Bound::Included(value), } } /// Returns true if the value is within this range. pub fn contains(&self, value: &T) -> bool { // Exclude by start bound match &self.start { Bound::Included(s) if value < s => return false, Bound::Excluded(s) if value <= s => return false, _ => {} } // Exclude by end bound match &self.end { Bound::Included(e) if value > e => return false, Bound::Excluded(e) if value >= e => return false, _ => {} } // If not excluded by either bound, the value is within the range true } } impl RangeBounds for RangeCfg { fn start_bound(&self) -> Bound<&T> { self.start.as_ref() } fn end_bound(&self) -> Bound<&T> { self.end.as_ref() } } #[cfg(test)] mod tests { use super::*; use core::ops::Bound::{Excluded, Included, Unbounded}; #[test] fn test_range_cfg_from() { // Full range let cfg_full: RangeCfg = (..).into(); assert_eq!( cfg_full, RangeCfg { start: Unbounded, end: Unbounded } ); // Start bounded, end unbounded let cfg_start_incl: RangeCfg = (5..).into(); assert_eq!( cfg_start_incl, RangeCfg { start: Included(5), end: Unbounded } ); // Start unbounded, end bounded (exclusive) let cfg_end_excl: RangeCfg = (..10).into(); assert_eq!( cfg_end_excl, RangeCfg { start: Unbounded, end: Excluded(10) } ); // Start unbounded, end bounded (inclusive) let cfg_end_incl: RangeCfg = (..=10).into(); assert_eq!( cfg_end_incl, RangeCfg { start: Unbounded, end: Included(10) } ); // Fully bounded (inclusive start, exclusive end) let cfg_incl_excl: RangeCfg = (5..10).into(); assert_eq!( cfg_incl_excl, RangeCfg { start: Included(5), end: Excluded(10) } ); // Fully bounded (inclusive) let cfg_incl_incl: RangeCfg = (5..=10).into(); assert_eq!( cfg_incl_incl, RangeCfg { start: Included(5), end: Included(10) } ); // Fully bounded (exclusive start) struct ExclusiveStartRange(usize, usize); impl RangeBounds for ExclusiveStartRange { fn start_bound(&self) -> Bound<&usize> { Excluded(&self.0) } fn end_bound(&self) -> Bound<&usize> { Included(&self.1) } } let cfg_excl_incl = RangeCfg::new(ExclusiveStartRange(5, 10)); assert_eq!( cfg_excl_incl, RangeCfg { start: Excluded(5), end: Included(10) } ); } #[test] fn test_range_cfg_contains() { // Unbounded range (..) let cfg_unbounded: RangeCfg = (..).into(); assert!(cfg_unbounded.contains(&0)); assert!(cfg_unbounded.contains(&100)); assert!(cfg_unbounded.contains(&usize::MAX)); // Inclusive start (5..) let cfg_start_incl: RangeCfg = (5..).into(); assert!(!cfg_start_incl.contains(&4)); assert!(cfg_start_incl.contains(&5)); assert!(cfg_start_incl.contains(&6)); assert!(cfg_start_incl.contains(&usize::MAX)); // Exclusive end (..10) let cfg_end_excl: RangeCfg = (..10).into(); assert!(cfg_end_excl.contains(&0)); assert!(cfg_end_excl.contains(&9)); assert!(!cfg_end_excl.contains(&10)); assert!(!cfg_end_excl.contains(&11)); // Inclusive end (..=10) let cfg_end_incl: RangeCfg = (..=10).into(); assert!(cfg_end_incl.contains(&0)); assert!(cfg_end_incl.contains(&9)); assert!(cfg_end_incl.contains(&10)); assert!(!cfg_end_incl.contains(&11)); // Inclusive start, exclusive end (5..10) let cfg_incl_excl: RangeCfg = (5..10).into(); assert!(!cfg_incl_excl.contains(&4)); assert!(cfg_incl_excl.contains(&5)); assert!(cfg_incl_excl.contains(&9)); assert!(!cfg_incl_excl.contains(&10)); assert!(!cfg_incl_excl.contains(&11)); // Inclusive start, inclusive end (5..=10) let cfg_incl_incl: RangeCfg = (5..=10).into(); assert!(!cfg_incl_incl.contains(&4)); assert!(cfg_incl_incl.contains(&5)); assert!(cfg_incl_incl.contains(&9)); assert!(cfg_incl_incl.contains(&10)); assert!(!cfg_incl_incl.contains(&11)); // Exclusive start, inclusive end (pseudo: >5 ..=10) let cfg_excl_incl = RangeCfg { start: Excluded(5), end: Included(10), }; assert!(!cfg_excl_incl.contains(&4)); assert!(!cfg_excl_incl.contains(&5)); // Excluded assert!(cfg_excl_incl.contains(&6)); assert!(cfg_excl_incl.contains(&10)); // Included assert!(!cfg_excl_incl.contains(&11)); // Exclusive start, exclusive end (pseudo: >5 .. <10) let cfg_excl_excl = RangeCfg { start: Excluded(5), end: Excluded(10), }; assert!(!cfg_excl_excl.contains(&5)); // Excluded assert!(cfg_excl_excl.contains(&6)); assert!(cfg_excl_excl.contains(&9)); assert!(!cfg_excl_excl.contains(&10)); // Excluded } #[test] fn test_contains_empty_range() { // Empty range (e.g., 5..5) let cfg_empty_excl: RangeCfg = (5..5).into(); assert!(!cfg_empty_excl.contains(&4)); assert!(!cfg_empty_excl.contains(&5)); assert!(!cfg_empty_excl.contains(&6)); // Slightly less obvious empty range (e.g., 6..=5) #[allow(clippy::reversed_empty_ranges)] let cfg_empty_incl: RangeCfg = (6..=5).into(); assert!(!cfg_empty_incl.contains(&5)); assert!(!cfg_empty_incl.contains(&6)); } #[test] fn test_range_cfg_u8() { // Test with u8 type let cfg = RangeCfg::new(0u8..=255u8); assert!(cfg.contains(&0)); assert!(cfg.contains(&128)); assert!(cfg.contains(&255)); let cfg_partial = RangeCfg::new(10u8..20u8); assert!(!cfg_partial.contains(&9)); assert!(cfg_partial.contains(&10)); assert!(cfg_partial.contains(&19)); assert!(!cfg_partial.contains(&20)); } #[test] fn test_range_cfg_u16() { // Test with u16 type let cfg = RangeCfg::new(100u16..=1000u16); assert!(!cfg.contains(&99)); assert!(cfg.contains(&100)); assert!(cfg.contains(&500)); assert!(cfg.contains(&1000)); assert!(!cfg.contains(&1001)); } #[test] fn test_range_cfg_u32() { // Test with u32 type let cfg = RangeCfg::new(0u32..1024u32); assert!(cfg.contains(&0)); assert!(cfg.contains(&512)); assert!(!cfg.contains(&1024)); assert!(!cfg.contains(&2000)); } #[test] fn test_range_cfg_u64() { // Test with u64 type let cfg = RangeCfg::new(1000u64..); assert!(!cfg.contains(&999)); assert!(cfg.contains(&1000)); assert!(cfg.contains(&u64::MAX)); } #[test] fn test_type_inference() { // Type inference from range literal with explicit type suffixes let cfg = RangeCfg::new(0u8..10u8); assert!(cfg.contains(&5u8)); // Type inference when assigning to typed variable let cfg: RangeCfg = RangeCfg::new(0..1000); assert!(cfg.contains(&500)); } }