1use std::cmp::{max, min};
7
8use thiserror::Error;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum RangeSpec {
13 FromTo { start: u64, end: u64 },
15 From { start: u64 },
17 Suffix { length: u64 },
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct SatisfiableRange {
24 pub start: u64,
25 pub end: u64,
26}
27
28impl SatisfiableRange {
29 pub fn len(self) -> u64 {
30 self.end - self.start + 1
31 }
32}
33
34#[derive(Debug, Error, PartialEq, Eq)]
36pub enum RangeParseError {
37 #[error("range unit must be bytes")]
38 InvalidUnit,
39 #[error("invalid range syntax")]
40 InvalidSyntax,
41 #[error("range start greater than end")]
42 InvalidBounds,
43 #[error("range component is not numeric")]
44 InvalidNumber,
45 #[error("empty range set")]
46 Empty,
47}
48
49pub fn parse_range_header(value: &str) -> Result<Vec<RangeSpec>, RangeParseError> {
51 let (unit, ranges) = value
52 .split_once('=')
53 .ok_or(RangeParseError::InvalidSyntax)?;
54 if !unit.trim().eq_ignore_ascii_case("bytes") {
55 return Err(RangeParseError::InvalidUnit);
56 }
57
58 let mut specs = Vec::new();
59 for part in ranges.split(',') {
60 let part = part.trim();
61 if part.is_empty() {
62 continue;
63 }
64 if let Some(stripped) = part.strip_prefix('-') {
65 let length = stripped
66 .trim()
67 .parse::<u64>()
68 .map_err(|_| RangeParseError::InvalidNumber)?;
69 specs.push(RangeSpec::Suffix { length });
70 continue;
71 }
72
73 let mut bounds = part.splitn(2, '-');
74 let start = bounds
75 .next()
76 .ok_or(RangeParseError::InvalidSyntax)?
77 .trim()
78 .parse::<u64>()
79 .map_err(|_| RangeParseError::InvalidNumber)?;
80
81 match bounds.next() {
82 Some("") | None => {
83 specs.push(RangeSpec::From { start });
84 }
85 Some(end_str) => {
86 let end = end_str
87 .trim()
88 .parse::<u64>()
89 .map_err(|_| RangeParseError::InvalidNumber)?;
90 if start > end {
91 return Err(RangeParseError::InvalidBounds);
92 }
93 specs.push(RangeSpec::FromTo { start, end });
94 }
95 }
96 }
97
98 if specs.is_empty() {
99 return Err(RangeParseError::Empty);
100 }
101
102 Ok(specs)
103}
104
105pub fn compute_satisfiable_ranges(specs: &[RangeSpec], total_length: u64) -> Vec<SatisfiableRange> {
108 if total_length == 0 {
109 return Vec::new();
110 }
111
112 specs
113 .iter()
114 .filter_map(|spec| match spec {
115 RangeSpec::FromTo { start, end } => {
116 if *start >= total_length {
117 None
118 } else {
119 let bounded_end = min(*end, total_length - 1);
120 Some(SatisfiableRange {
121 start: *start,
122 end: max(*start, bounded_end),
123 })
124 }
125 }
126 RangeSpec::From { start } => {
127 if *start >= total_length {
128 None
129 } else {
130 Some(SatisfiableRange {
131 start: *start,
132 end: total_length - 1,
133 })
134 }
135 }
136 RangeSpec::Suffix { length } => {
137 let length = min(*length, total_length);
138 if length == 0 {
139 None
140 } else {
141 Some(SatisfiableRange {
142 start: total_length - length,
143 end: total_length - 1,
144 })
145 }
146 }
147 })
148 .collect()
149}
150
151pub fn format_content_range(range: SatisfiableRange, total_length: u64) -> String {
153 format!("bytes {}-{}/{}", range.start, range.end, total_length)
154}
155
156pub fn format_unsatisfied_range(total_length: u64) -> String {
158 format!("bytes */{}", total_length)
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn parse_single_range() {
167 let parsed = parse_range_header("bytes=0-99").unwrap();
168 assert_eq!(parsed, vec![RangeSpec::FromTo { start: 0, end: 99 }]);
169 }
170
171 #[test]
172 fn parse_suffix_range() {
173 let parsed = parse_range_header("bytes=-500").unwrap();
174 assert_eq!(parsed, vec![RangeSpec::Suffix { length: 500 }]);
175 }
176
177 #[test]
178 fn compute_satisfiable_from_range() {
179 let specs = parse_range_header("bytes=100-").unwrap();
180 let ranges = compute_satisfiable_ranges(&specs, 1000);
181 assert_eq!(
182 ranges,
183 vec![SatisfiableRange {
184 start: 100,
185 end: 999
186 }]
187 );
188 }
189
190 #[test]
191 fn unsatisfiable_returns_empty() {
192 let specs = parse_range_header("bytes=2000-3000").unwrap();
193 let ranges = compute_satisfiable_ranges(&specs, 1024);
194 assert!(ranges.is_empty());
195 }
196}