commonware_codec/
config.rs

1//! Types for use as [crate::Read::Cfg].
2
3use core::{
4    num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, 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_nonzero {
80    ($nz_ty:ty, $ty:ty) => {
81        impl From<RangeCfg<$nz_ty>> for RangeCfg<$ty> {
82            fn from(value: RangeCfg<$nz_ty>) -> Self {
83                let start = match value.start {
84                    Bound::Included(nz) => Bound::Included(nz.get()),
85                    Bound::Excluded(nz) => Bound::Excluded(nz.get()),
86                    Bound::Unbounded => Bound::Unbounded,
87                };
88                let end = match value.end {
89                    Bound::Included(nz) => Bound::Included(nz.get()),
90                    Bound::Excluded(nz) => Bound::Excluded(nz.get()),
91                    Bound::Unbounded => Bound::Unbounded,
92                };
93                RangeCfg { start, end }
94            }
95        }
96    };
97}
98
99impl_from_nonzero!(NonZeroUsize, usize);
100impl_from_nonzero!(NonZeroU8, u8);
101impl_from_nonzero!(NonZeroU16, u16);
102impl_from_nonzero!(NonZeroU32, u32);
103impl_from_nonzero!(NonZeroU64, u64);
104
105macro_rules! impl_from_nonzero_to_usize {
106    ($from_ty:ty) => {
107        impl From<RangeCfg<$from_ty>> for RangeCfg<usize> {
108            fn from(value: RangeCfg<$from_ty>) -> Self {
109                let start = match value.start {
110                    Bound::Included(v) => Bound::Included(
111                        usize::try_from(v.get()).expect("range start exceeds usize"),
112                    ),
113                    Bound::Excluded(v) => Bound::Excluded(
114                        usize::try_from(v.get()).expect("range start exceeds usize"),
115                    ),
116                    Bound::Unbounded => Bound::Unbounded,
117                };
118                let end = match value.end {
119                    Bound::Included(v) => {
120                        Bound::Included(usize::try_from(v.get()).expect("range end exceeds usize"))
121                    }
122                    Bound::Excluded(v) => {
123                        Bound::Excluded(usize::try_from(v.get()).expect("range end exceeds usize"))
124                    }
125                    Bound::Unbounded => Bound::Unbounded,
126                };
127                RangeCfg { start, end }
128            }
129        }
130    };
131}
132
133impl_from_nonzero_to_usize!(NonZeroU8);
134impl_from_nonzero_to_usize!(NonZeroU16);
135impl_from_nonzero_to_usize!(NonZeroU32);
136
137macro_rules! impl_nonzero_to_nonzero_usize {
138    ($from_ty:ty) => {
139        impl From<RangeCfg<$from_ty>> for RangeCfg<NonZeroUsize> {
140            fn from(value: RangeCfg<$from_ty>) -> Self {
141                let start = match value.start {
142                    Bound::Included(v) => Bound::Included(
143                        NonZeroUsize::try_from(v).expect("range start exceeds usize"),
144                    ),
145                    Bound::Excluded(v) => Bound::Excluded(
146                        NonZeroUsize::try_from(v).expect("range start exceeds usize"),
147                    ),
148                    Bound::Unbounded => Bound::Unbounded,
149                };
150                let end = match value.end {
151                    Bound::Included(v) => {
152                        Bound::Included(NonZeroUsize::try_from(v).expect("range end exceeds usize"))
153                    }
154                    Bound::Excluded(v) => {
155                        Bound::Excluded(NonZeroUsize::try_from(v).expect("range end exceeds usize"))
156                    }
157                    Bound::Unbounded => Bound::Unbounded,
158                };
159                RangeCfg { start, end }
160            }
161        }
162    };
163}
164
165impl_nonzero_to_nonzero_usize!(NonZeroU8);
166impl_nonzero_to_nonzero_usize!(NonZeroU16);
167impl_nonzero_to_nonzero_usize!(NonZeroU32);
168
169impl<T: Copy + PartialOrd> RangeCfg<T> {
170    /// Creates a new `RangeCfg` from any type implementing `RangeBounds<T>`.
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use commonware_codec::RangeCfg;
176    ///
177    /// let cfg = RangeCfg::new(0..=1024);
178    /// assert!(cfg.contains(&500));
179    /// ```
180    pub fn new(r: impl RangeBounds<T>) -> Self {
181        Self {
182            start: r.start_bound().cloned(),
183            end: r.end_bound().cloned(),
184        }
185    }
186
187    /// Creates a `RangeCfg` that only accepts exactly `value`.
188    pub const fn exact(value: T) -> Self {
189        Self {
190            start: Bound::Included(value),
191            end: Bound::Included(value),
192        }
193    }
194
195    /// Returns true if the value is within this range.
196    pub fn contains(&self, value: &T) -> bool {
197        // Exclude by start bound
198        match &self.start {
199            Bound::Included(s) if value < s => return false,
200            Bound::Excluded(s) if value <= s => return false,
201            _ => {}
202        }
203
204        // Exclude by end bound
205        match &self.end {
206            Bound::Included(e) if value > e => return false,
207            Bound::Excluded(e) if value >= e => return false,
208            _ => {}
209        }
210
211        // If not excluded by either bound, the value is within the range
212        true
213    }
214}
215
216impl<T: Copy + PartialOrd> RangeBounds<T> for RangeCfg<T> {
217    fn start_bound(&self) -> Bound<&T> {
218        self.start.as_ref()
219    }
220
221    fn end_bound(&self) -> Bound<&T> {
222        self.end.as_ref()
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    use core::ops::Bound::{Excluded, Included, Unbounded};
230
231    #[test]
232    fn test_range_cfg_from() {
233        // Full range
234        let cfg_full: RangeCfg<usize> = (..).into();
235        assert_eq!(
236            cfg_full,
237            RangeCfg {
238                start: Unbounded,
239                end: Unbounded
240            }
241        );
242
243        // Start bounded, end unbounded
244        let cfg_start_incl: RangeCfg<usize> = (5..).into();
245        assert_eq!(
246            cfg_start_incl,
247            RangeCfg {
248                start: Included(5),
249                end: Unbounded
250            }
251        );
252
253        // Start unbounded, end bounded (exclusive)
254        let cfg_end_excl: RangeCfg<usize> = (..10).into();
255        assert_eq!(
256            cfg_end_excl,
257            RangeCfg {
258                start: Unbounded,
259                end: Excluded(10)
260            }
261        );
262
263        // Start unbounded, end bounded (inclusive)
264        let cfg_end_incl: RangeCfg<usize> = (..=10).into();
265        assert_eq!(
266            cfg_end_incl,
267            RangeCfg {
268                start: Unbounded,
269                end: Included(10)
270            }
271        );
272
273        // Fully bounded (inclusive start, exclusive end)
274        let cfg_incl_excl: RangeCfg<usize> = (5..10).into();
275        assert_eq!(
276            cfg_incl_excl,
277            RangeCfg {
278                start: Included(5),
279                end: Excluded(10)
280            }
281        );
282
283        // Fully bounded (inclusive)
284        let cfg_incl_incl: RangeCfg<usize> = (5..=10).into();
285        assert_eq!(
286            cfg_incl_incl,
287            RangeCfg {
288                start: Included(5),
289                end: Included(10)
290            }
291        );
292
293        // Fully bounded (exclusive start)
294        struct ExclusiveStartRange(usize, usize);
295        impl RangeBounds<usize> for ExclusiveStartRange {
296            fn start_bound(&self) -> Bound<&usize> {
297                Excluded(&self.0)
298            }
299            fn end_bound(&self) -> Bound<&usize> {
300                Included(&self.1)
301            }
302        }
303        let cfg_excl_incl = RangeCfg::new(ExclusiveStartRange(5, 10));
304        assert_eq!(
305            cfg_excl_incl,
306            RangeCfg {
307                start: Excluded(5),
308                end: Included(10)
309            }
310        );
311    }
312
313    #[test]
314    fn test_range_cfg_contains() {
315        // Unbounded range (..)
316        let cfg_unbounded: RangeCfg<usize> = (..).into();
317        assert!(cfg_unbounded.contains(&0));
318        assert!(cfg_unbounded.contains(&100));
319        assert!(cfg_unbounded.contains(&usize::MAX));
320
321        // Inclusive start (5..)
322        let cfg_start_incl: RangeCfg<usize> = (5..).into();
323        assert!(!cfg_start_incl.contains(&4));
324        assert!(cfg_start_incl.contains(&5));
325        assert!(cfg_start_incl.contains(&6));
326        assert!(cfg_start_incl.contains(&usize::MAX));
327
328        // Exclusive end (..10)
329        let cfg_end_excl: RangeCfg<usize> = (..10).into();
330        assert!(cfg_end_excl.contains(&0));
331        assert!(cfg_end_excl.contains(&9));
332        assert!(!cfg_end_excl.contains(&10));
333        assert!(!cfg_end_excl.contains(&11));
334
335        // Inclusive end (..=10)
336        let cfg_end_incl: RangeCfg<usize> = (..=10).into();
337        assert!(cfg_end_incl.contains(&0));
338        assert!(cfg_end_incl.contains(&9));
339        assert!(cfg_end_incl.contains(&10));
340        assert!(!cfg_end_incl.contains(&11));
341
342        // Inclusive start, exclusive end (5..10)
343        let cfg_incl_excl: RangeCfg<usize> = (5..10).into();
344        assert!(!cfg_incl_excl.contains(&4));
345        assert!(cfg_incl_excl.contains(&5));
346        assert!(cfg_incl_excl.contains(&9));
347        assert!(!cfg_incl_excl.contains(&10));
348        assert!(!cfg_incl_excl.contains(&11));
349
350        // Inclusive start, inclusive end (5..=10)
351        let cfg_incl_incl: RangeCfg<usize> = (5..=10).into();
352        assert!(!cfg_incl_incl.contains(&4));
353        assert!(cfg_incl_incl.contains(&5));
354        assert!(cfg_incl_incl.contains(&9));
355        assert!(cfg_incl_incl.contains(&10));
356        assert!(!cfg_incl_incl.contains(&11));
357
358        // Exclusive start, inclusive end (pseudo: >5 ..=10)
359        let cfg_excl_incl = RangeCfg {
360            start: Excluded(5),
361            end: Included(10),
362        };
363        assert!(!cfg_excl_incl.contains(&4));
364        assert!(!cfg_excl_incl.contains(&5)); // Excluded
365        assert!(cfg_excl_incl.contains(&6));
366        assert!(cfg_excl_incl.contains(&10)); // Included
367        assert!(!cfg_excl_incl.contains(&11));
368
369        // Exclusive start, exclusive end (pseudo: >5 .. <10)
370        let cfg_excl_excl = RangeCfg {
371            start: Excluded(5),
372            end: Excluded(10),
373        };
374        assert!(!cfg_excl_excl.contains(&5)); // Excluded
375        assert!(cfg_excl_excl.contains(&6));
376        assert!(cfg_excl_excl.contains(&9));
377        assert!(!cfg_excl_excl.contains(&10)); // Excluded
378    }
379
380    #[test]
381    fn test_contains_empty_range() {
382        // Empty range (e.g., 5..5)
383        let cfg_empty_excl: RangeCfg<usize> = (5..5).into();
384        assert!(!cfg_empty_excl.contains(&4));
385        assert!(!cfg_empty_excl.contains(&5));
386        assert!(!cfg_empty_excl.contains(&6));
387
388        // Slightly less obvious empty range (e.g., 6..=5)
389        #[allow(clippy::reversed_empty_ranges)]
390        let cfg_empty_incl: RangeCfg<usize> = (6..=5).into();
391        assert!(!cfg_empty_incl.contains(&5));
392        assert!(!cfg_empty_incl.contains(&6));
393    }
394
395    #[test]
396    fn test_range_cfg_u8() {
397        // Test with u8 type
398        let cfg = RangeCfg::new(0u8..=255u8);
399        assert!(cfg.contains(&0));
400        assert!(cfg.contains(&128));
401        assert!(cfg.contains(&255));
402
403        let cfg_partial = RangeCfg::new(10u8..20u8);
404        assert!(!cfg_partial.contains(&9));
405        assert!(cfg_partial.contains(&10));
406        assert!(cfg_partial.contains(&19));
407        assert!(!cfg_partial.contains(&20));
408    }
409
410    #[test]
411    fn test_range_cfg_u16() {
412        // Test with u16 type
413        let cfg = RangeCfg::new(100u16..=1000u16);
414        assert!(!cfg.contains(&99));
415        assert!(cfg.contains(&100));
416        assert!(cfg.contains(&500));
417        assert!(cfg.contains(&1000));
418        assert!(!cfg.contains(&1001));
419    }
420
421    #[test]
422    fn test_range_cfg_u32() {
423        // Test with u32 type
424        let cfg = RangeCfg::new(0u32..1024u32);
425        assert!(cfg.contains(&0));
426        assert!(cfg.contains(&512));
427        assert!(!cfg.contains(&1024));
428        assert!(!cfg.contains(&2000));
429    }
430
431    #[test]
432    fn test_range_cfg_u64() {
433        // Test with u64 type
434        let cfg = RangeCfg::new(1000u64..);
435        assert!(!cfg.contains(&999));
436        assert!(cfg.contains(&1000));
437        assert!(cfg.contains(&u64::MAX));
438    }
439
440    #[test]
441    fn test_type_inference() {
442        // Type inference from range literal with explicit type suffixes
443        let cfg = RangeCfg::new(0u8..10u8);
444        assert!(cfg.contains(&5u8));
445
446        // Type inference when assigning to typed variable
447        let cfg: RangeCfg<u32> = RangeCfg::new(0..1000);
448        assert!(cfg.contains(&500));
449    }
450}