1use serde::{Deserialize, Serialize};
7use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14pub struct Range {
15 pub offset: i64,
17 pub limit_to: Option<i64>,
19}
20
21impl Default for Range {
22 fn default() -> Self {
24 Self::all()
25 }
26}
27
28impl Range {
29 pub fn all() -> Self {
31 Self {
32 offset: 0,
33 limit_to: None,
34 }
35 }
36
37 pub fn new(offset: i64, limit_to: i64) -> Self {
39 Self {
40 offset,
41 limit_to: Some(limit_to),
42 }
43 }
44
45 pub fn from_offset(offset: i64) -> Self {
47 Self {
48 offset,
49 limit_to: None,
50 }
51 }
52
53 pub fn limit_zero() -> Self {
56 Self {
57 offset: 0,
58 limit_to: Some(-1),
59 }
60 }
61
62 pub fn has_limit_zero(&self) -> bool {
64 self.limit_to == Some(-1)
65 }
66
67 pub fn limit(&self) -> Option<i64> {
69 self.limit_to.map(|upper| 1 + upper - self.offset)
70 }
71
72 pub fn offset(&self) -> i64 {
74 self.offset
75 }
76
77 pub fn is_all(&self) -> bool {
79 self.offset == 0 && self.limit_to.is_none()
80 }
81
82 pub fn is_empty_range(&self) -> bool {
84 match self.limit_to {
85 Some(upper) => self.offset > upper && !self.has_limit_zero(),
86 None => false,
87 }
88 }
89
90 pub fn restrict(&self, max_rows: Option<i64>) -> Self {
94 match max_rows {
95 None => *self,
96 Some(limit) => {
97 let new_upper = self.offset + limit - 1;
98 match self.limit_to {
99 Some(upper) => Self {
100 offset: self.offset,
101 limit_to: Some(upper.min(new_upper)),
102 },
103 None => Self {
104 offset: self.offset,
105 limit_to: Some(new_upper),
106 },
107 }
108 }
109 }
110 }
111
112 pub fn with_limit(&self, limit: i64) -> Self {
114 Self {
115 offset: self.offset,
116 limit_to: Some(self.offset + limit - 1),
117 }
118 }
119
120 pub fn with_offset(&self, offset: i64) -> Self {
122 Self {
123 offset,
124 limit_to: self.limit_to,
125 }
126 }
127
128 pub fn intersect(&self, other: &Range) -> Self {
130 let new_offset = self.offset.max(other.offset);
131 let new_upper = match (self.limit_to, other.limit_to) {
132 (Some(a), Some(b)) => Some(a.min(b)),
133 (Some(a), None) => Some(a),
134 (None, Some(b)) => Some(b),
135 (None, None) => None,
136 };
137 Self {
138 offset: new_offset,
139 limit_to: new_upper,
140 }
141 }
142
143 pub fn convert_to_limit_zero(&self, fallback: &Range) -> Self {
145 if self.has_limit_zero() {
146 Self::limit_zero()
147 } else {
148 *fallback
149 }
150 }
151}
152
153impl fmt::Display for Range {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 match self.limit_to {
156 Some(upper) => write!(f, "{}-{}", self.offset, upper),
157 None => write!(f, "{}-", self.offset),
158 }
159 }
160}
161
162pub fn parse_range_header(header: &str) -> Option<Range> {
180 let range_str = header
182 .strip_prefix("items=")
183 .or_else(|| header.strip_prefix("Items="))?;
184
185 let (start_str, end_str) = range_str.split_once('-')?;
186
187 let start: i64 = start_str.parse().ok()?;
188
189 if end_str.is_empty() {
190 Some(Range::from_offset(start))
191 } else {
192 let end: i64 = end_str.parse().ok()?;
193 Some(Range::new(start, end))
194 }
195}
196
197pub fn content_range_header(lower: i64, upper: i64, total: Option<i64>) -> String {
201 let total_str = match total {
202 Some(t) => t.to_string(),
203 None => "*".to_string(),
204 };
205
206 let total_not_zero = total != Some(0);
207 let from_in_range = lower <= upper;
208
209 if total_not_zero && from_in_range {
210 format!("{}-{}/{}", lower, upper, total_str)
211 } else {
212 format!("*/{}", total_str)
213 }
214}
215
216#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn test_range_all() {
226 let r = Range::all();
227 assert_eq!(r.offset, 0);
228 assert_eq!(r.limit_to, None);
229 assert!(r.is_all());
230 assert!(!r.is_empty_range());
231 }
232
233 #[test]
234 fn test_range_new() {
235 let r = Range::new(0, 24);
236 assert_eq!(r.offset, 0);
237 assert_eq!(r.limit_to, Some(24));
238 assert!(!r.is_all());
239 assert_eq!(r.limit(), Some(25));
240 }
241
242 #[test]
243 fn test_range_from_offset() {
244 let r = Range::from_offset(10);
245 assert_eq!(r.offset, 10);
246 assert_eq!(r.limit_to, None);
247 assert!(!r.is_all());
248 }
249
250 #[test]
251 fn test_range_limit_zero() {
252 let r = Range::limit_zero();
253 assert!(r.has_limit_zero());
254 assert_eq!(r.limit(), Some(0));
255 }
256
257 #[test]
258 fn test_range_empty() {
259 let r = Range::new(10, 5); assert!(r.is_empty_range());
261 }
262
263 #[test]
264 fn test_range_restrict() {
265 let r = Range::all();
266 let restricted = r.restrict(Some(25));
267 assert_eq!(restricted.offset, 0);
268 assert_eq!(restricted.limit_to, Some(24));
269
270 let same = r.restrict(None);
272 assert_eq!(same, r);
273 }
274
275 #[test]
276 fn test_range_restrict_existing() {
277 let r = Range::new(0, 100);
278 let restricted = r.restrict(Some(25));
279 assert_eq!(restricted.limit_to, Some(24));
280
281 let larger = r.restrict(Some(200));
283 assert_eq!(larger.limit_to, Some(100));
284 }
285
286 #[test]
287 fn test_range_with_limit() {
288 let r = Range::from_offset(5);
289 let limited = r.with_limit(10);
290 assert_eq!(limited.offset, 5);
291 assert_eq!(limited.limit_to, Some(14));
292 assert_eq!(limited.limit(), Some(10));
293 }
294
295 #[test]
296 fn test_range_with_offset() {
297 let r = Range::new(0, 24);
298 let offset = r.with_offset(10);
299 assert_eq!(offset.offset, 10);
300 assert_eq!(offset.limit_to, Some(24));
301 }
302
303 #[test]
304 fn test_range_intersect() {
305 let a = Range::new(0, 100);
306 let b = Range::new(10, 50);
307 let c = a.intersect(&b);
308 assert_eq!(c.offset, 10);
309 assert_eq!(c.limit_to, Some(50));
310
311 let d = Range::all();
313 let e = a.intersect(&d);
314 assert_eq!(e, a);
315 }
316
317 #[test]
318 fn test_range_display() {
319 assert_eq!(Range::new(0, 24).to_string(), "0-24");
320 assert_eq!(Range::from_offset(10).to_string(), "10-");
321 }
322
323 #[test]
324 fn test_parse_range_header() {
325 let r = parse_range_header("items=0-24").unwrap();
326 assert_eq!(r.offset, 0);
327 assert_eq!(r.limit_to, Some(24));
328
329 let r = parse_range_header("items=10-").unwrap();
330 assert_eq!(r.offset, 10);
331 assert_eq!(r.limit_to, None);
332
333 let r = parse_range_header("Items=5-10").unwrap();
334 assert_eq!(r.offset, 5);
335 assert_eq!(r.limit_to, Some(10));
336
337 assert!(parse_range_header("bytes=0-24").is_none());
339 assert!(parse_range_header("items=abc-def").is_none());
340 assert!(parse_range_header("garbage").is_none());
341 }
342
343 #[test]
344 fn test_content_range_header() {
345 assert_eq!(content_range_header(0, 24, Some(100)), "0-24/100");
346 assert_eq!(content_range_header(0, 24, None), "0-24/*");
347 assert_eq!(content_range_header(10, 5, Some(100)), "*/100"); assert_eq!(content_range_header(0, 0, Some(0)), "*/0"); }
350
351 #[test]
352 fn test_range_default() {
353 let r = Range::default();
354 assert!(r.is_all());
355 }
356
357 #[test]
358 fn test_convert_to_limit_zero() {
359 let limit_range = Range::limit_zero();
360 let fallback = Range::new(0, 24);
361 let result = limit_range.convert_to_limit_zero(&fallback);
362 assert!(result.has_limit_zero());
363
364 let normal = Range::new(0, 10);
365 let result2 = normal.convert_to_limit_zero(&fallback);
366 assert_eq!(result2, fallback);
367 }
368}