Skip to main content

commonware_codec/
config.rs

1//! Types for use as [crate::Read::Cfg].
2
3use core::{
4    num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroUsize},
5    ops::{Bound, RangeBounds},
6};
7
8/// Configuration for limiting the range of a value.
9///
10/// This is often used to configure length limits for variable-length types or collections.
11///
12/// # Examples
13///
14/// ```
15/// use commonware_codec::RangeCfg;
16///
17/// // Limit lengths to 0..=1024 (type inferred as usize)
18/// let cfg = RangeCfg::new(0..=1024);
19/// assert!(cfg.contains(&500));
20/// assert!(!cfg.contains(&2000));
21///
22/// // Allow any length >= 1
23/// let cfg_min = RangeCfg::from(1..);
24/// assert!(cfg_min.contains(&1));
25/// assert!(!cfg_min.contains(&0));
26///
27/// // Works with other integer types
28/// let cfg_u8: RangeCfg<u8> = RangeCfg::new(0u8..=255u8);
29/// assert!(cfg_u8.contains(&128));
30///
31/// let cfg_u32 = RangeCfg::new(0u32..1024u32);
32/// assert!(cfg_u32.contains(&500));
33/// ```
34#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
35pub struct RangeCfg<T: Copy + PartialOrd> {
36    /// The lower bound of the range.
37    start: Bound<T>,
38
39    /// The upper bound of the range.
40    end: Bound<T>,
41}
42
43impl<T: Copy + PartialOrd> From<core::ops::Range<T>> for RangeCfg<T> {
44    fn from(r: core::ops::Range<T>) -> Self {
45        Self::new(r)
46    }
47}
48
49impl<T: Copy + PartialOrd> From<core::ops::RangeInclusive<T>> for RangeCfg<T> {
50    fn from(r: core::ops::RangeInclusive<T>) -> Self {
51        Self::new(r)
52    }
53}
54
55impl<T: Copy + PartialOrd> From<core::ops::RangeFrom<T>> for RangeCfg<T> {
56    fn from(r: core::ops::RangeFrom<T>) -> Self {
57        Self::new(r)
58    }
59}
60
61impl<T: Copy + PartialOrd> From<core::ops::RangeTo<T>> for RangeCfg<T> {
62    fn from(r: core::ops::RangeTo<T>) -> Self {
63        Self::new(r)
64    }
65}
66
67impl<T: Copy + PartialOrd> From<core::ops::RangeToInclusive<T>> for RangeCfg<T> {
68    fn from(r: core::ops::RangeToInclusive<T>) -> Self {
69        Self::new(r)
70    }
71}
72
73impl<T: Copy + PartialOrd> From<core::ops::RangeFull> for RangeCfg<T> {
74    fn from(_: core::ops::RangeFull) -> Self {
75        Self::new(..)
76    }
77}
78
79macro_rules! impl_from {
80    ($from:ty => $to:ty, $f:expr) => {
81        impl From<RangeCfg<$from>> for RangeCfg<$to> {
82            fn from(value: RangeCfg<$from>) -> Self {
83                value.map($f)
84            }
85        }
86    };
87}
88
89impl_from!(NonZeroUsize => usize, usize::from);
90impl_from!(NonZeroU8 => u8, u8::from);
91impl_from!(NonZeroU16 => u16, u16::from);
92impl_from!(NonZeroU32 => u32, u32::from);
93impl_from!(NonZeroU64 => u64, u64::from);
94
95impl_from!(NonZeroU8 => usize, |v| usize::from(v.get()));
96impl_from!(NonZeroU16 => usize, |v| usize::from(v.get()));
97impl_from!(NonZeroU32 => usize, |v| usize::try_from(v.get()).expect("range bound exceeds usize"));
98
99impl_from!(NonZeroU8 => NonZeroUsize, NonZeroUsize::from);
100impl_from!(NonZeroU16 => NonZeroUsize, NonZeroUsize::from);
101impl_from!(NonZeroU32 => NonZeroUsize, |v| {
102    NonZeroUsize::try_from(v).expect("range bound exceeds usize")
103});
104
105impl<T: Copy + PartialOrd> RangeCfg<T> {
106    /// Creates a new `RangeCfg` from any type implementing `RangeBounds<T>`.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use commonware_codec::RangeCfg;
112    ///
113    /// let cfg = RangeCfg::new(0..=1024);
114    /// assert!(cfg.contains(&500));
115    /// ```
116    pub fn new(r: impl RangeBounds<T>) -> Self {
117        Self {
118            start: r.start_bound().cloned(),
119            end: r.end_bound().cloned(),
120        }
121    }
122
123    /// Creates a `RangeCfg` that only accepts exactly `value`.
124    pub const fn exact(value: T) -> Self {
125        Self {
126            start: Bound::Included(value),
127            end: Bound::Included(value),
128        }
129    }
130
131    /// Returns true if the value is within this range.
132    pub fn contains(&self, value: &T) -> bool {
133        // Exclude by start bound
134        match &self.start {
135            Bound::Included(s) if value < s => return false,
136            Bound::Excluded(s) if value <= s => return false,
137            _ => {}
138        }
139
140        // Exclude by end bound
141        match &self.end {
142            Bound::Included(e) if value > e => return false,
143            Bound::Excluded(e) if value >= e => return false,
144            _ => {}
145        }
146
147        // If not excluded by either bound, the value is within the range
148        true
149    }
150
151    /// Applies `f` to both bounds.
152    fn map<U: Copy + PartialOrd>(self, f: impl Fn(T) -> U) -> RangeCfg<U> {
153        RangeCfg {
154            start: self.start.map(&f),
155            end: self.end.map(&f),
156        }
157    }
158}
159
160impl<T: Copy + PartialOrd> RangeBounds<T> for RangeCfg<T> {
161    fn start_bound(&self) -> Bound<&T> {
162        self.start.as_ref()
163    }
164
165    fn end_bound(&self) -> Bound<&T> {
166        self.end.as_ref()
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    use core::ops::Bound::{Excluded, Included, Unbounded};
174
175    #[test]
176    fn test_range_cfg_from() {
177        // Full range
178        let cfg_full: RangeCfg<usize> = (..).into();
179        assert_eq!(
180            cfg_full,
181            RangeCfg {
182                start: Unbounded,
183                end: Unbounded
184            }
185        );
186
187        // Start bounded, end unbounded
188        let cfg_start_incl: RangeCfg<usize> = (5..).into();
189        assert_eq!(
190            cfg_start_incl,
191            RangeCfg {
192                start: Included(5),
193                end: Unbounded
194            }
195        );
196
197        // Start unbounded, end bounded (exclusive)
198        let cfg_end_excl: RangeCfg<usize> = (..10).into();
199        assert_eq!(
200            cfg_end_excl,
201            RangeCfg {
202                start: Unbounded,
203                end: Excluded(10)
204            }
205        );
206
207        // Start unbounded, end bounded (inclusive)
208        let cfg_end_incl: RangeCfg<usize> = (..=10).into();
209        assert_eq!(
210            cfg_end_incl,
211            RangeCfg {
212                start: Unbounded,
213                end: Included(10)
214            }
215        );
216
217        // Fully bounded (inclusive start, exclusive end)
218        let cfg_incl_excl: RangeCfg<usize> = (5..10).into();
219        assert_eq!(
220            cfg_incl_excl,
221            RangeCfg {
222                start: Included(5),
223                end: Excluded(10)
224            }
225        );
226
227        // Fully bounded (inclusive)
228        let cfg_incl_incl: RangeCfg<usize> = (5..=10).into();
229        assert_eq!(
230            cfg_incl_incl,
231            RangeCfg {
232                start: Included(5),
233                end: Included(10)
234            }
235        );
236
237        // Fully bounded (exclusive start)
238        struct ExclusiveStartRange(usize, usize);
239        impl RangeBounds<usize> for ExclusiveStartRange {
240            fn start_bound(&self) -> Bound<&usize> {
241                Excluded(&self.0)
242            }
243            fn end_bound(&self) -> Bound<&usize> {
244                Included(&self.1)
245            }
246        }
247        let cfg_excl_incl = RangeCfg::new(ExclusiveStartRange(5, 10));
248        assert_eq!(
249            cfg_excl_incl,
250            RangeCfg {
251                start: Excluded(5),
252                end: Included(10)
253            }
254        );
255    }
256
257    #[test]
258    fn test_range_cfg_contains() {
259        // Unbounded range (..)
260        let cfg_unbounded: RangeCfg<usize> = (..).into();
261        assert!(cfg_unbounded.contains(&0));
262        assert!(cfg_unbounded.contains(&100));
263        assert!(cfg_unbounded.contains(&usize::MAX));
264
265        // Inclusive start (5..)
266        let cfg_start_incl: RangeCfg<usize> = (5..).into();
267        assert!(!cfg_start_incl.contains(&4));
268        assert!(cfg_start_incl.contains(&5));
269        assert!(cfg_start_incl.contains(&6));
270        assert!(cfg_start_incl.contains(&usize::MAX));
271
272        // Exclusive end (..10)
273        let cfg_end_excl: RangeCfg<usize> = (..10).into();
274        assert!(cfg_end_excl.contains(&0));
275        assert!(cfg_end_excl.contains(&9));
276        assert!(!cfg_end_excl.contains(&10));
277        assert!(!cfg_end_excl.contains(&11));
278
279        // Inclusive end (..=10)
280        let cfg_end_incl: RangeCfg<usize> = (..=10).into();
281        assert!(cfg_end_incl.contains(&0));
282        assert!(cfg_end_incl.contains(&9));
283        assert!(cfg_end_incl.contains(&10));
284        assert!(!cfg_end_incl.contains(&11));
285
286        // Inclusive start, exclusive end (5..10)
287        let cfg_incl_excl: RangeCfg<usize> = (5..10).into();
288        assert!(!cfg_incl_excl.contains(&4));
289        assert!(cfg_incl_excl.contains(&5));
290        assert!(cfg_incl_excl.contains(&9));
291        assert!(!cfg_incl_excl.contains(&10));
292        assert!(!cfg_incl_excl.contains(&11));
293
294        // Inclusive start, inclusive end (5..=10)
295        let cfg_incl_incl: RangeCfg<usize> = (5..=10).into();
296        assert!(!cfg_incl_incl.contains(&4));
297        assert!(cfg_incl_incl.contains(&5));
298        assert!(cfg_incl_incl.contains(&9));
299        assert!(cfg_incl_incl.contains(&10));
300        assert!(!cfg_incl_incl.contains(&11));
301
302        // Exclusive start, inclusive end (pseudo: >5 ..=10)
303        let cfg_excl_incl = RangeCfg {
304            start: Excluded(5),
305            end: Included(10),
306        };
307        assert!(!cfg_excl_incl.contains(&4));
308        assert!(!cfg_excl_incl.contains(&5)); // Excluded
309        assert!(cfg_excl_incl.contains(&6));
310        assert!(cfg_excl_incl.contains(&10)); // Included
311        assert!(!cfg_excl_incl.contains(&11));
312
313        // Exclusive start, exclusive end (pseudo: >5 .. <10)
314        let cfg_excl_excl = RangeCfg {
315            start: Excluded(5),
316            end: Excluded(10),
317        };
318        assert!(!cfg_excl_excl.contains(&5)); // Excluded
319        assert!(cfg_excl_excl.contains(&6));
320        assert!(cfg_excl_excl.contains(&9));
321        assert!(!cfg_excl_excl.contains(&10)); // Excluded
322    }
323
324    #[test]
325    fn test_contains_empty_range() {
326        // Empty range (e.g., 5..5)
327        let cfg_empty_excl: RangeCfg<usize> = (5..5).into();
328        assert!(!cfg_empty_excl.contains(&4));
329        assert!(!cfg_empty_excl.contains(&5));
330        assert!(!cfg_empty_excl.contains(&6));
331
332        // Slightly less obvious empty range (e.g., 6..=5)
333        #[allow(clippy::reversed_empty_ranges)]
334        let cfg_empty_incl: RangeCfg<usize> = (6..=5).into();
335        assert!(!cfg_empty_incl.contains(&5));
336        assert!(!cfg_empty_incl.contains(&6));
337    }
338
339    #[test]
340    fn test_range_cfg_u8() {
341        // Test with u8 type
342        let cfg = RangeCfg::new(0u8..=255u8);
343        assert!(cfg.contains(&0));
344        assert!(cfg.contains(&128));
345        assert!(cfg.contains(&255));
346
347        let cfg_partial = RangeCfg::new(10u8..20u8);
348        assert!(!cfg_partial.contains(&9));
349        assert!(cfg_partial.contains(&10));
350        assert!(cfg_partial.contains(&19));
351        assert!(!cfg_partial.contains(&20));
352    }
353
354    #[test]
355    fn test_range_cfg_u16() {
356        // Test with u16 type
357        let cfg = RangeCfg::new(100u16..=1000u16);
358        assert!(!cfg.contains(&99));
359        assert!(cfg.contains(&100));
360        assert!(cfg.contains(&500));
361        assert!(cfg.contains(&1000));
362        assert!(!cfg.contains(&1001));
363    }
364
365    #[test]
366    fn test_range_cfg_u32() {
367        // Test with u32 type
368        let cfg = RangeCfg::new(0u32..1024u32);
369        assert!(cfg.contains(&0));
370        assert!(cfg.contains(&512));
371        assert!(!cfg.contains(&1024));
372        assert!(!cfg.contains(&2000));
373    }
374
375    #[test]
376    fn test_range_cfg_u64() {
377        // Test with u64 type
378        let cfg = RangeCfg::new(1000u64..);
379        assert!(!cfg.contains(&999));
380        assert!(cfg.contains(&1000));
381        assert!(cfg.contains(&u64::MAX));
382    }
383
384    #[test]
385    fn test_type_inference() {
386        // Type inference from range literal with explicit type suffixes
387        let cfg = RangeCfg::new(0u8..10u8);
388        assert!(cfg.contains(&5u8));
389
390        // Type inference when assigning to typed variable
391        let cfg: RangeCfg<u32> = RangeCfg::new(0..1000);
392        assert!(cfg.contains(&500));
393    }
394}