commonware_codec/
config.rs

1//! Types for use as [crate::Read::Cfg].
2
3use core::ops::{Bound, RangeBounds};
4
5/// Configuration for limiting the range of a [usize] value.
6///
7/// This is often used to configure length limits for variable-length types or collections.
8///
9/// # Example
10///
11/// ```
12/// use commonware_codec::RangeCfg;
13///
14/// // Limit lengths to 0..=1024
15/// let cfg = RangeCfg::from(0..=1024);
16/// assert!(cfg.contains(&500));
17/// assert!(!cfg.contains(&2000));
18///
19/// // Allow any length >= 1
20/// let cfg_min = RangeCfg::from(1..);
21/// assert!(cfg_min.contains(&1));
22/// assert!(!cfg_min.contains(&0));
23/// ```
24#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
25pub struct RangeCfg {
26    /// The lower bound of the range.
27    start: Bound<usize>,
28
29    /// The upper bound of the range.
30    end: Bound<usize>,
31}
32
33impl RangeCfg {
34    /// Returns `true` if the given value is within the configured range.
35    pub fn contains(&self, value: &usize) -> bool {
36        // Exclude by start bound
37        match &self.start {
38            Bound::Included(s) if value < s => return false,
39            Bound::Excluded(s) if value <= s => return false,
40            _ => {}
41        }
42
43        // Exclude by end bound
44        match &self.end {
45            Bound::Included(e) if value > e => return false,
46            Bound::Excluded(e) if value >= e => return false,
47            _ => {}
48        }
49
50        // If not excluded by either bound, the value is within the range
51        true
52    }
53}
54
55// Allow conversion from any type that implements `RangeBounds<usize>` to `RangeCfg`.
56impl<R: RangeBounds<usize>> From<R> for RangeCfg {
57    fn from(r: R) -> Self {
58        fn own(b: Bound<&usize>) -> Bound<usize> {
59            match b {
60                Bound::Included(&v) => Bound::Included(v),
61                Bound::Excluded(&v) => Bound::Excluded(v),
62                Bound::Unbounded => Bound::Unbounded,
63            }
64        }
65
66        RangeCfg {
67            start: own(r.start_bound()),
68            end: own(r.end_bound()),
69        }
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use core::ops::Bound::{Excluded, Included, Unbounded};
77
78    #[test]
79    fn test_range_cfg_from() {
80        // Full range
81        let cfg_full: RangeCfg = (..).into();
82        assert_eq!(
83            cfg_full,
84            RangeCfg {
85                start: Unbounded,
86                end: Unbounded
87            }
88        );
89
90        // Start bounded, end unbounded
91        let cfg_start_incl: RangeCfg = (5..).into();
92        assert_eq!(
93            cfg_start_incl,
94            RangeCfg {
95                start: Included(5),
96                end: Unbounded
97            }
98        );
99
100        // Start unbounded, end bounded (exclusive)
101        let cfg_end_excl: RangeCfg = (..10).into();
102        assert_eq!(
103            cfg_end_excl,
104            RangeCfg {
105                start: Unbounded,
106                end: Excluded(10)
107            }
108        );
109
110        // Start unbounded, end bounded (inclusive)
111        let cfg_end_incl: RangeCfg = (..=10).into();
112        assert_eq!(
113            cfg_end_incl,
114            RangeCfg {
115                start: Unbounded,
116                end: Included(10)
117            }
118        );
119
120        // Fully bounded (inclusive start, exclusive end)
121        let cfg_incl_excl: RangeCfg = (5..10).into();
122        assert_eq!(
123            cfg_incl_excl,
124            RangeCfg {
125                start: Included(5),
126                end: Excluded(10)
127            }
128        );
129
130        // Fully bounded (inclusive)
131        let cfg_incl_incl: RangeCfg = (5..=10).into();
132        assert_eq!(
133            cfg_incl_incl,
134            RangeCfg {
135                start: Included(5),
136                end: Included(10)
137            }
138        );
139
140        // Fully bounded (exclusive start)
141        struct ExclusiveStartRange(usize, usize);
142        impl RangeBounds<usize> for ExclusiveStartRange {
143            fn start_bound(&self) -> Bound<&usize> {
144                Excluded(&self.0)
145            }
146            fn end_bound(&self) -> Bound<&usize> {
147                Included(&self.1)
148            }
149        }
150        let cfg_excl_incl: RangeCfg = ExclusiveStartRange(5, 10).into();
151        assert_eq!(
152            cfg_excl_incl,
153            RangeCfg {
154                start: Excluded(5),
155                end: Included(10)
156            }
157        );
158    }
159
160    #[test]
161    fn test_range_cfg_contains() {
162        // Unbounded range (..)
163        let cfg_unbounded: RangeCfg = (..).into();
164        assert!(cfg_unbounded.contains(&0));
165        assert!(cfg_unbounded.contains(&100));
166        assert!(cfg_unbounded.contains(&usize::MAX));
167
168        // Inclusive start (5..)
169        let cfg_start_incl: RangeCfg = (5..).into();
170        assert!(!cfg_start_incl.contains(&4));
171        assert!(cfg_start_incl.contains(&5));
172        assert!(cfg_start_incl.contains(&6));
173        assert!(cfg_start_incl.contains(&usize::MAX));
174
175        // Exclusive end (..10)
176        let cfg_end_excl: RangeCfg = (..10).into();
177        assert!(cfg_end_excl.contains(&0));
178        assert!(cfg_end_excl.contains(&9));
179        assert!(!cfg_end_excl.contains(&10));
180        assert!(!cfg_end_excl.contains(&11));
181
182        // Inclusive end (..=10)
183        let cfg_end_incl: RangeCfg = (..=10).into();
184        assert!(cfg_end_incl.contains(&0));
185        assert!(cfg_end_incl.contains(&9));
186        assert!(cfg_end_incl.contains(&10));
187        assert!(!cfg_end_incl.contains(&11));
188
189        // Inclusive start, exclusive end (5..10)
190        let cfg_incl_excl: RangeCfg = (5..10).into();
191        assert!(!cfg_incl_excl.contains(&4));
192        assert!(cfg_incl_excl.contains(&5));
193        assert!(cfg_incl_excl.contains(&9));
194        assert!(!cfg_incl_excl.contains(&10));
195        assert!(!cfg_incl_excl.contains(&11));
196
197        // Inclusive start, inclusive end (5..=10)
198        let cfg_incl_incl: RangeCfg = (5..=10).into();
199        assert!(!cfg_incl_incl.contains(&4));
200        assert!(cfg_incl_incl.contains(&5));
201        assert!(cfg_incl_incl.contains(&9));
202        assert!(cfg_incl_incl.contains(&10));
203        assert!(!cfg_incl_incl.contains(&11));
204
205        // Exclusive start, inclusive end (pseudo: >5 ..=10)
206        let cfg_excl_incl = RangeCfg {
207            start: Excluded(5),
208            end: Included(10),
209        };
210        assert!(!cfg_excl_incl.contains(&4));
211        assert!(!cfg_excl_incl.contains(&5)); // Excluded
212        assert!(cfg_excl_incl.contains(&6));
213        assert!(cfg_excl_incl.contains(&10)); // Included
214        assert!(!cfg_excl_incl.contains(&11));
215
216        // Exclusive start, exclusive end (pseudo: >5 .. <10)
217        let cfg_excl_excl = RangeCfg {
218            start: Excluded(5),
219            end: Excluded(10),
220        };
221        assert!(!cfg_excl_excl.contains(&5)); // Excluded
222        assert!(cfg_excl_excl.contains(&6));
223        assert!(cfg_excl_excl.contains(&9));
224        assert!(!cfg_excl_excl.contains(&10)); // Excluded
225    }
226
227    #[test]
228    fn test_contains_empty_range() {
229        // Empty range (e.g., 5..5)
230        let cfg_empty_excl: RangeCfg = (5..5).into();
231        assert!(!cfg_empty_excl.contains(&4));
232        assert!(!cfg_empty_excl.contains(&5));
233        assert!(!cfg_empty_excl.contains(&6));
234
235        // Slightly less obvious empty range (e.g., 6..=5)
236        #[allow(clippy::reversed_empty_ranges)]
237        let cfg_empty_incl: RangeCfg = (6..=5).into();
238        assert!(!cfg_empty_incl.contains(&5));
239        assert!(!cfg_empty_incl.contains(&6));
240    }
241}