azure_storage_blob/models/
http_ranges.rs1use azure_core::error::{Error, ErrorKind, ResultExt};
5use azure_core::http::headers::{Header, HeaderName, HeaderValue};
6use std::fmt;
7use std::ops::{Range, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive};
8use std::str::FromStr;
9
10const PREFIX: &str = "bytes ";
11const WILDCARD: &str = "*";
12const CONTENT_RANGE_ID: HeaderName = HeaderName::from_static("content-range");
13
14type Result<T> = azure_core::Result<T>;
15
16#[derive(Debug, Copy, Clone, PartialEq, Eq)]
18pub(crate) struct ContentRange {
19 pub range: Option<(usize, usize)>,
21 pub total_len: Option<usize>,
23}
24
25impl Header for ContentRange {
26 fn name(&self) -> azure_core::http::headers::HeaderName {
27 CONTENT_RANGE_ID
28 }
29
30 fn value(&self) -> azure_core::http::headers::HeaderValue {
31 let range_str = match self.range {
32 Some(range) => format!("{}-{}", range.0, range.1),
33 None => WILDCARD.to_string(),
34 };
35 let len_str = match self.total_len {
36 Some(len) => len.to_string(),
37 None => WILDCARD.to_string(),
38 };
39 format!("{}{}/{}", PREFIX, range_str, len_str).into()
40 }
41}
42
43impl FromStr for ContentRange {
44 type Err = Error;
45 fn from_str(s: &str) -> Result<ContentRange> {
46 let remaining = s.strip_prefix(PREFIX).ok_or_else(|| {
47 Error::with_message_fn(ErrorKind::Other, || {
48 format!(
49 "expected token \"{PREFIX}\" not found when parsing ContentRange from \"{s}\""
50 )
51 })
52 })?;
53
54 let mut split_at_slash = remaining.split('/');
55
56 let range = parse_range(split_at_slash.next().ok_or_else(|| {
57 Error::with_message(ErrorKind::Other, "Unexpected end of Content-Range.")
58 })?)?;
59
60 let total_len = parse_total_length(split_at_slash.next().ok_or_else(|| {
61 Error::with_message_fn(ErrorKind::Other, || {
62 format!(
63 "expected token \"{}\" not found when parsing ContentRange from \"{}\"",
64 "/", s
65 )
66 })
67 })?)?;
68
69 Ok(ContentRange { range, total_len })
70 }
71}
72
73fn parse_range(s: &str) -> Result<Option<(usize, usize)>> {
78 let s = s.trim();
79 if s == WILDCARD {
80 return Ok(None);
81 }
82
83 let mut split_at_dash = s.split('-');
84 let start = split_at_dash
85 .next()
86 .ok_or_else(|| Error::with_message(ErrorKind::Other, "Unexpected end of Content-Range."))?
87 .parse::<usize>()
88 .with_kind(ErrorKind::DataConversion)?;
89 let end = split_at_dash
90 .next()
91 .ok_or_else(|| {
92 Error::with_message_fn(ErrorKind::Other, || {
93 format!(
94 "expected token \"{}\" not found when parsing ContentRange from \"{}\"",
95 "-", s
96 )
97 })
98 })?
99 .parse::<usize>()
100 .with_kind(ErrorKind::DataConversion)?;
101
102 Ok(Some((start, end + 1)))
103}
104
105fn parse_total_length(s: &str) -> Result<Option<usize>> {
106 let s = s.trim();
107 if s == WILDCARD {
108 return Ok(None);
109 }
110 Ok(Some(s.parse().with_kind(ErrorKind::DataConversion)?))
111}
112
113impl fmt::Display for ContentRange {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 write!(
116 f,
117 "{}{}/{}",
118 PREFIX,
119 self.range
120 .map(|range| format!("{}-{}", range.0, range.1 - 1))
121 .unwrap_or(WILDCARD.into()),
122 self.total_len
123 .map(|len| len.to_string())
124 .unwrap_or(WILDCARD.into()),
125 )
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn parse() {
135 let range = "bytes 172032-172489/172490"
136 .parse::<ContentRange>()
137 .unwrap();
138
139 assert_eq!(range.range.unwrap().0, 172032);
140 assert_eq!(range.range.unwrap().1, 172490);
141 assert_eq!(range.total_len.unwrap(), 172490);
142 }
143
144 #[test]
145 fn parse_no_starting_token() {
146 "something else".parse::<ContentRange>().unwrap_err();
147 }
148
149 #[test]
150 fn parse_no_dash() {
151 "bytes 100".parse::<ContentRange>().unwrap_err();
152 }
153
154 #[test]
155 fn parse_no_slash() {
156 "bytes 100-500".parse::<ContentRange>().unwrap_err();
157 }
158
159 #[test]
160 fn display() {
161 let range = ContentRange {
162 range: Some((100, 500)),
163 total_len: Some(5000),
164 };
165
166 let txt = format!("{range}");
167
168 assert_eq!(txt, "bytes 100-499/5000");
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
213pub struct HttpRange {
214 offset: u64,
216 length: Option<u64>,
218}
219
220impl HttpRange {
221 pub fn new(offset: u64, length: u64) -> Self {
230 Self {
231 offset,
232 length: Some(length),
233 }
234 }
235
236 pub fn from_offset(offset: u64) -> Self {
242 Self {
243 offset,
244 length: None,
245 }
246 }
247
248 pub(crate) fn offset(&self) -> u64 {
249 self.offset
250 }
251
252 pub(crate) fn length(&self) -> Option<u64> {
253 self.length
254 }
255}
256
257impl fmt::Display for HttpRange {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 match self.length {
260 Some(length) => write!(
261 f,
262 "bytes={}-{}",
263 self.offset,
264 self.offset.saturating_add(length).saturating_sub(1)
265 ),
266 None => write!(f, "bytes={}-", self.offset),
267 }
268 }
269}
270
271impl From<HttpRange> for HeaderValue {
272 fn from(range: HttpRange) -> Self {
273 HeaderValue::from(range.to_string())
274 }
275}
276
277impl From<Range<u64>> for HttpRange {
280 fn from(range: Range<u64>) -> Self {
281 Self::new(range.start, range.end - range.start)
282 }
283}
284
285impl From<RangeFrom<u64>> for HttpRange {
286 fn from(range: RangeFrom<u64>) -> Self {
287 Self::from_offset(range.start)
288 }
289}
290
291impl From<RangeInclusive<u64>> for HttpRange {
292 fn from(range: RangeInclusive<u64>) -> Self {
293 Self::new(*range.start(), range.end() - range.start() + 1)
294 }
295}
296
297impl From<RangeTo<u64>> for HttpRange {
298 fn from(range: RangeTo<u64>) -> Self {
299 Self::new(0, range.end)
300 }
301}
302
303impl From<RangeToInclusive<u64>> for HttpRange {
304 fn from(range: RangeToInclusive<u64>) -> Self {
305 Self::new(0, range.end + 1)
306 }
307}
308
309impl From<Range<usize>> for HttpRange {
312 fn from(range: Range<usize>) -> Self {
313 Self::new(range.start as u64, (range.end - range.start) as u64)
314 }
315}
316
317impl From<RangeFrom<usize>> for HttpRange {
318 fn from(range: RangeFrom<usize>) -> Self {
319 Self::from_offset(range.start as u64)
320 }
321}
322
323impl From<RangeInclusive<usize>> for HttpRange {
324 fn from(range: RangeInclusive<usize>) -> Self {
325 Self::new(
326 *range.start() as u64,
327 (range.end() - range.start() + 1) as u64,
328 )
329 }
330}
331
332impl From<RangeTo<usize>> for HttpRange {
333 fn from(range: RangeTo<usize>) -> Self {
334 Self::new(0, range.end as u64)
335 }
336}
337
338impl From<RangeToInclusive<usize>> for HttpRange {
339 fn from(range: RangeToInclusive<usize>) -> Self {
340 Self::new(0, (range.end + 1) as u64)
341 }
342}
343
344#[cfg(test)]
345mod http_range_tests {
346 use super::*;
347
348 #[test]
349 fn new_creates_bounded_range() {
350 let range = HttpRange::new(0, 512);
351 assert_eq!(range.to_string(), "bytes=0-511");
352 }
353
354 #[test]
355 fn from_offset_creates_open_ended_range() {
356 let range = HttpRange::from_offset(255);
357 assert_eq!(range.to_string(), "bytes=255-");
358 }
359
360 #[test]
361 fn display_bounded_range() {
362 let range = HttpRange::new(0, 512);
363 assert_eq!(range.to_string(), "bytes=0-511");
364 }
365
366 #[test]
367 fn display_open_ended_range() {
368 let range = HttpRange::from_offset(255);
369 assert_eq!(range.to_string(), "bytes=255-");
370 }
371
372 #[test]
373 fn to_string_bounded_range() {
374 let range = HttpRange::new(100, 101);
375 assert_eq!(range.to_string(), "bytes=100-200");
376 }
377
378 #[test]
379 fn into_header_value() {
380 let range = HttpRange::new(0, 512);
381 let header_value: HeaderValue = range.into();
382 assert_eq!(header_value.as_str(), "bytes=0-511");
383 }
384
385 #[test]
386 fn display_zero_length_does_not_panic() {
387 let range = HttpRange::new(0, 0);
389 let _ = range.to_string();
391 }
392
393 #[test]
394 fn display_overflow_does_not_panic() {
395 let range = HttpRange::new(u64::MAX, u64::MAX);
397 let _ = range.to_string();
398 }
399
400 #[test]
403 fn from_range_u64() {
404 let range: HttpRange = (0u64..100).into();
405 assert_eq!(range.to_string(), "bytes=0-99");
406 }
407
408 #[test]
409 fn from_range_from_u64() {
410 let range: HttpRange = (100u64..).into();
411 assert_eq!(range.to_string(), "bytes=100-");
412 }
413
414 #[test]
415 fn from_range_inclusive_u64() {
416 let range: HttpRange = (0u64..=99).into();
417 assert_eq!(range.to_string(), "bytes=0-99");
418 }
419
420 #[test]
421 fn from_range_to_u64() {
422 let range: HttpRange = (..100u64).into();
423 assert_eq!(range.to_string(), "bytes=0-99");
424 }
425
426 #[test]
427 fn from_range_to_inclusive_u64() {
428 let range: HttpRange = (..=99u64).into();
429 assert_eq!(range.to_string(), "bytes=0-99");
430 }
431
432 #[test]
435 fn from_range_usize() {
436 let range: HttpRange = (0usize..100).into();
437 assert_eq!(range.to_string(), "bytes=0-99");
438 }
439
440 #[test]
441 fn from_range_from_usize() {
442 let range: HttpRange = (100usize..).into();
443 assert_eq!(range.to_string(), "bytes=100-");
444 }
445
446 #[test]
447 fn from_range_inclusive_usize() {
448 let range: HttpRange = (0usize..=99).into();
449 assert_eq!(range.to_string(), "bytes=0-99");
450 }
451
452 #[test]
453 fn from_range_to_usize() {
454 let range: HttpRange = (..100usize).into();
455 assert_eq!(range.to_string(), "bytes=0-99");
456 }
457
458 #[test]
459 fn from_range_to_inclusive_usize() {
460 let range: HttpRange = (..=99usize).into();
461 assert_eq!(range.to_string(), "bytes=0-99");
462 }
463
464 #[test]
465 fn from_range_nonzero_offset() {
466 let exclusive: HttpRange = (50u64..150).into();
468 let inclusive: HttpRange = (50u64..=149).into();
469 assert_eq!(exclusive.to_string(), "bytes=50-149");
470 assert_eq!(inclusive.to_string(), "bytes=50-149");
471 assert_eq!(exclusive, inclusive);
472 }
473
474 #[test]
475 fn from_range_single_byte() {
476 let exclusive: HttpRange = (42u64..43).into();
478 let inclusive: HttpRange = (42u64..=42).into();
479 assert_eq!(exclusive.to_string(), "bytes=42-42");
480 assert_eq!(inclusive.to_string(), "bytes=42-42");
481 assert_eq!(exclusive, inclusive);
482 }
483}