1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub struct RangeSpec {
12 pub start: u64,
14 pub end: u64,
16}
17
18impl RangeSpec {
19 pub fn new(start: u64, len: u64) -> Self {
21 RangeSpec {
22 start,
23 end: start.saturating_add(len),
24 }
25 }
26
27 pub fn full(len: u64) -> Self {
29 RangeSpec { start: 0, end: len }
30 }
31
32 pub fn len(&self) -> u64 {
34 self.end.saturating_sub(self.start)
35 }
36
37 pub fn is_empty(&self) -> bool {
39 self.len() == 0
40 }
41
42 pub fn clamp(&self, file_size: u64) -> Option<RangeSpec> {
47 if self.start >= file_size {
48 return None;
49 }
50 Some(RangeSpec {
51 start: self.start,
52 end: self.end.min(file_size),
53 })
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn basic_range() {
63 let r = RangeSpec::new(100, 50);
64 assert_eq!((r.start, r.end, r.len()), (100, 150, 50));
65 }
66
67 #[test]
68 fn clamp_limits_end() {
69 let r = RangeSpec::new(0, 1000).clamp(100).unwrap();
70 assert_eq!((r.start, r.end), (0, 100));
71 }
72
73 #[test]
74 fn clamp_rejects_past_end() {
75 assert!(RangeSpec::new(100, 10).clamp(99).is_none());
76 }
77
78 #[test]
79 fn clamp_rejects_zero_file() {
80 assert!(RangeSpec::full(0).clamp(0).is_none());
81 }
82}