simulator_api/
slot_range.rs1use std::fmt;
9
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
16pub struct SlotRange {
17 start: u64,
18 end: u64,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
23#[error("invalid range: start_slot {start} > end_slot {end}")]
24pub struct InvertedSlotRange {
25 pub start: u64,
26 pub end: u64,
27}
28
29impl SlotRange {
30 pub fn new(start: u64, end: u64) -> Result<Self, InvertedSlotRange> {
32 if start > end {
33 return Err(InvertedSlotRange { start, end });
34 }
35 Ok(Self { start, end })
36 }
37
38 pub const fn new_unchecked(start: u64, end: u64) -> Self {
41 Self { start, end }
42 }
43
44 pub const fn from_count(start: u64, count: u64) -> Self {
47 Self {
48 start,
49 end: start.saturating_add(count.saturating_sub(1)),
50 }
51 }
52
53 pub const fn start(&self) -> u64 {
54 self.start
55 }
56
57 pub const fn end(&self) -> u64 {
58 self.end
59 }
60
61 pub const fn len(&self) -> u64 {
63 self.end - self.start + 1
64 }
65
66 pub const fn is_empty(&self) -> bool {
69 false
70 }
71
72 pub const fn contains(&self, slot: u64) -> bool {
73 slot >= self.start && slot <= self.end
74 }
75
76 pub const fn overlaps(&self, other: SlotRange) -> bool {
77 self.start <= other.end && other.start <= self.end
78 }
79}
80
81impl fmt::Display for SlotRange {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 write!(f, "{}..={}", self.start, self.end)
84 }
85}
86
87impl From<SlotRange> for std::ops::RangeInclusive<u64> {
88 fn from(range: SlotRange) -> Self {
89 range.start..=range.end
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use rstest::rstest;
96
97 use super::*;
98
99 #[test]
100 fn an_inverted_range_is_rejected() {
101 assert_eq!(
102 SlotRange::new(10, 9),
103 Err(InvertedSlotRange { start: 10, end: 9 })
104 );
105 assert!(SlotRange::new(10, 10).is_ok());
106 }
107
108 #[test]
109 fn length_is_inclusive_of_both_bounds() {
110 assert_eq!(SlotRange::new(10, 10).expect("range").len(), 1);
111 assert_eq!(SlotRange::new(10, 19).expect("range").len(), 10);
112 }
113
114 #[test]
115 fn from_count_matches_the_start_plus_count_convention() {
116 assert_eq!(
117 SlotRange::from_count(100, 1),
118 SlotRange::new_unchecked(100, 100)
119 );
120 assert_eq!(
121 SlotRange::from_count(100, 10),
122 SlotRange::new_unchecked(100, 109)
123 );
124 assert_eq!(
126 SlotRange::from_count(100, 0),
127 SlotRange::new_unchecked(100, 100)
128 );
129 }
130
131 #[test]
132 fn contains_is_inclusive() {
133 let range = SlotRange::new(10, 12).expect("range");
134 assert!(range.contains(10) && range.contains(12));
135 assert!(!range.contains(9) && !range.contains(13));
136 }
137
138 #[rstest]
139 #[case::shares_the_lower_bound(5, 10, true)]
140 #[case::shares_the_upper_bound(12, 20, true)]
141 #[case::lies_inside(11, 11, true)]
142 #[case::encloses(0, 20, true)]
143 #[case::ends_just_below(5, 9, false)]
144 #[case::starts_just_above(13, 20, false)]
145 fn overlaps_is_inclusive(#[case] start: u64, #[case] end: u64, #[case] expected: bool) {
146 let range = SlotRange::new_unchecked(10, 12);
147 let other = SlotRange::new_unchecked(start, end);
148 assert_eq!(range.overlaps(other), expected);
149 assert_eq!(other.overlaps(range), expected);
150 }
151}