azure_core/request_options/
content_range.rs
1use crate::error::{Error, ErrorKind, ResultExt};
2use std::fmt;
3use std::str::FromStr;
4
5const PREFIX: &str = "bytes ";
6
7#[derive(Debug, Copy, Clone, PartialEq, Eq)]
8pub struct ContentRange {
9 start: u64,
10 end: u64,
11 total_length: u64,
12}
13
14impl ContentRange {
15 pub fn new(start: u64, end: u64, total_length: u64) -> ContentRange {
16 ContentRange {
17 start,
18 end,
19 total_length,
20 }
21 }
22
23 pub fn start(&self) -> u64 {
24 self.start
25 }
26
27 pub fn end(&self) -> u64 {
28 self.end
29 }
30
31 pub fn total_length(&self) -> u64 {
32 self.total_length
33 }
34
35 pub fn is_empty(&self) -> bool {
36 self.end == self.start
37 }
38}
39
40impl FromStr for ContentRange {
41 type Err = Error;
42 fn from_str(s: &str) -> crate::Result<ContentRange> {
43 let remaining = s.strip_prefix(PREFIX).ok_or_else(|| {
44 Error::with_message(ErrorKind::Other, || {
45 format!(
46 "expected token \"{PREFIX}\" not found when parsing ContentRange from \"{s}\""
47 )
48 })
49 })?;
50
51 if cfg!(feature = "azurite_workaround") && remaining == "0--1/0" {
54 return Ok(ContentRange {
55 start: 0,
56 end: 0,
57 total_length: 0,
58 });
59 }
60
61 let mut split_at_dash = remaining.split('-');
62 let start = split_at_dash
63 .next()
64 .ok_or_else(|| {
65 Error::with_message(ErrorKind::Other, || {
66 format!(
67 "expected token \"{}\" not found when parsing ContentRange from \"{}\"",
68 "-", s
69 )
70 })
71 })?
72 .parse()
73 .map_kind(ErrorKind::DataConversion)?;
74
75 let mut split_at_slash = split_at_dash
76 .next()
77 .ok_or_else(|| {
78 Error::with_message(ErrorKind::Other, || {
79 format!(
80 "expected token \"{}\" not found when parsing ContentRange from \"{}\"",
81 "-", s
82 )
83 })
84 })?
85 .split('/');
86
87 let end = split_at_slash
88 .next()
89 .ok_or_else(|| {
90 Error::with_message(ErrorKind::Other, || {
91 format!(
92 "expected token \"{}\" not found when parsing ContentRange from \"{}\"",
93 "/", s
94 )
95 })
96 })?
97 .parse()
98 .map_kind(ErrorKind::DataConversion)?;
99
100 let total_length = split_at_slash
101 .next()
102 .ok_or_else(|| {
103 Error::with_message(ErrorKind::Other, || {
104 format!(
105 "expected token \"{}\" not found when parsing ContentRange from \"{}\"",
106 "/", s
107 )
108 })
109 })?
110 .parse()
111 .map_kind(ErrorKind::DataConversion)?;
112
113 Ok(ContentRange {
114 start,
115 end,
116 total_length,
117 })
118 }
119}
120
121impl fmt::Display for ContentRange {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(
124 f,
125 "{}{}-{}/{}",
126 PREFIX,
127 self.start(),
128 self.end(),
129 self.total_length()
130 )
131 }
132}
133
134#[cfg(test)]
135mod test {
136 use super::*;
137
138 #[cfg(feature = "azurite_workaround")]
139 #[test]
140 fn test_azurite_workaround() {
141 let range = "bytes 0--1/0".parse::<ContentRange>().unwrap();
142
143 assert_eq!(range.start(), 0);
144 assert_eq!(range.end(), 0);
145 assert_eq!(range.total_length(), 0);
146 }
147
148 #[test]
149 fn test_parse() {
150 let range = "bytes 172032-172489/172490"
151 .parse::<ContentRange>()
152 .unwrap();
153
154 assert_eq!(range.start(), 172032);
155 assert_eq!(range.end(), 172489);
156 assert_eq!(range.total_length(), 172490);
157 }
158
159 #[test]
160 fn test_parse_no_starting_token() {
161 "something else".parse::<ContentRange>().unwrap_err();
162 }
163
164 #[test]
165 fn test_parse_no_dash() {
166 "bytes 100".parse::<ContentRange>().unwrap_err();
167 }
168
169 #[test]
170 fn test_parse_no_slash() {
171 "bytes 100-500".parse::<ContentRange>().unwrap_err();
172 }
173
174 #[test]
175 fn test_display() {
176 let range = ContentRange {
177 start: 100,
178 end: 501,
179 total_length: 5000,
180 };
181
182 let txt = format!("{range}");
183
184 assert_eq!(txt, "bytes 100-501/5000");
185 }
186}