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