opendal_core/raw/http_util/
bytes_range.rs1use std::fmt::Debug;
19use std::fmt::Display;
20use std::fmt::Formatter;
21use std::ops::Bound;
22use std::ops::RangeBounds;
23use std::str::FromStr;
24
25use crate::*;
26
27#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
42pub struct BytesRange(
43 u64,
45 Option<u64>,
47);
48
49impl BytesRange {
50 pub fn new(offset: u64, size: Option<u64>) -> Self {
61 BytesRange(offset, size)
62 }
63
64 pub fn offset(&self) -> u64 {
66 self.0
67 }
68
69 pub fn size(&self) -> Option<u64> {
71 self.1
72 }
73
74 pub fn advance(&mut self, n: u64) {
81 self.0 = self
82 .0
83 .checked_add(n)
84 .expect("BytesRange::advance overflow: offset + n exceeds u64::MAX");
85 self.1 = self.1.map(|size| {
86 size.checked_sub(n)
87 .expect("BytesRange::advance underflow: n exceeds range size")
88 });
89 }
90
91 pub fn is_full(&self) -> bool {
95 self.0 == 0 && self.1.is_none()
96 }
97
98 pub fn to_header(&self) -> String {
100 format!("bytes={self}")
101 }
102
103 pub fn to_range(&self) -> impl RangeBounds<u64> {
105 (
106 Bound::Included(self.0),
107 match self.1 {
108 Some(size) => Bound::Excluded(
109 self.0
110 .checked_add(size)
111 .expect("BytesRange::to_range overflow: offset + size exceeds u64::MAX"),
112 ),
113 None => Bound::Unbounded,
114 },
115 )
116 }
117
118 pub fn to_range_as_usize(self) -> impl RangeBounds<usize> {
120 let offset: usize = self
121 .0
122 .try_into()
123 .expect("BytesRange::to_range_as_usize: offset exceeds usize::MAX");
124 (
125 Bound::Included(offset),
126 match self.1 {
127 Some(size) => {
128 let end: usize = self
129 .0
130 .checked_add(size)
131 .expect(
132 "BytesRange::to_range_as_usize overflow: offset + size exceeds u64::MAX",
133 )
134 .try_into()
135 .expect(
136 "BytesRange::to_range_as_usize: offset + size exceeds usize::MAX",
137 );
138 Bound::Excluded(end)
139 }
140 None => Bound::Unbounded,
141 },
142 )
143 }
144}
145
146impl Display for BytesRange {
147 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
148 match self.1 {
149 None => write!(f, "{}-", self.0),
150 Some(0) => Err(std::fmt::Error),
152 Some(size) => write!(
153 f,
154 "{}-{}",
155 self.0,
156 self.0.checked_add(size - 1).ok_or(std::fmt::Error)?
157 ),
158 }
159 }
160}
161
162impl FromStr for BytesRange {
163 type Err = Error;
164
165 fn from_str(value: &str) -> Result<Self> {
166 let s = value.strip_prefix("bytes=").ok_or_else(|| {
167 Error::new(ErrorKind::Unexpected, "header range is invalid")
168 .with_operation("BytesRange::from_str")
169 .with_context("value", value)
170 })?;
171
172 if s.contains(',') {
173 return Err(Error::new(ErrorKind::Unexpected, "header range is invalid")
174 .with_operation("BytesRange::from_str")
175 .with_context("value", value));
176 }
177
178 let v = s.split('-').collect::<Vec<_>>();
179 if v.len() != 2 {
180 return Err(Error::new(ErrorKind::Unexpected, "header range is invalid")
181 .with_operation("BytesRange::from_str")
182 .with_context("value", value));
183 }
184
185 let parse_int_error = |e: std::num::ParseIntError| {
186 Error::new(ErrorKind::Unexpected, "header range is invalid")
187 .with_operation("BytesRange::from_str")
188 .with_context("value", value)
189 .set_source(e)
190 };
191
192 if v[1].is_empty() {
193 Ok(BytesRange::new(
195 v[0].parse().map_err(parse_int_error)?,
196 None,
197 ))
198 } else if v[0].is_empty() {
199 Err(Error::new(
201 ErrorKind::Unexpected,
202 "header range with tailing is not supported",
203 )
204 .with_operation("BytesRange::from_str")
205 .with_context("value", value))
206 } else {
207 let start: u64 = v[0].parse().map_err(parse_int_error)?;
209 let end: u64 = v[1].parse().map_err(parse_int_error)?;
210 if end < start {
211 return Err(Error::new(
212 ErrorKind::Unexpected,
213 "header range is invalid: end is less than start",
214 )
215 .with_operation("BytesRange::from_str")
216 .with_context("value", value));
217 }
218 Ok(BytesRange::new(start, Some(end - start + 1)))
219 }
220 }
221}
222
223impl<T> From<T> for BytesRange
224where
225 T: RangeBounds<u64>,
226{
227 fn from(range: T) -> Self {
228 let offset = match range.start_bound().cloned() {
229 Bound::Included(n) => n,
230 Bound::Excluded(n) => n.saturating_add(1),
231 Bound::Unbounded => 0,
232 };
233 let size = match range.end_bound().cloned() {
234 Bound::Included(n) => Some(n.saturating_add(1).saturating_sub(offset)),
235 Bound::Excluded(n) => Some(n.saturating_sub(offset)),
236 Bound::Unbounded => None,
237 };
238
239 BytesRange(offset, size)
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn test_bytes_range_display_zero_size() {
249 let range = BytesRange::new(0, Some(0));
251 assert!(std::fmt::write(&mut String::new(), format_args!("{}", range)).is_err());
252
253 let range = BytesRange::new(5, Some(0));
255 assert!(std::fmt::write(&mut String::new(), format_args!("{}", range)).is_err());
256 }
257
258 #[test]
259 fn test_bytes_range_to_string() {
260 let h = BytesRange::new(0, Some(1024));
261 assert_eq!(h.to_string(), "0-1023");
262
263 let h = BytesRange::new(1024, None);
264 assert_eq!(h.to_string(), "1024-");
265
266 let h = BytesRange::new(1024, Some(1024));
267 assert_eq!(h.to_string(), "1024-2047");
268 }
269
270 #[test]
271 fn test_bytes_range_to_header() {
272 let h = BytesRange::new(0, Some(1024));
273 assert_eq!(h.to_header(), "bytes=0-1023");
274
275 let h = BytesRange::new(1024, None);
276 assert_eq!(h.to_header(), "bytes=1024-");
277
278 let h = BytesRange::new(1024, Some(1024));
279 assert_eq!(h.to_header(), "bytes=1024-2047");
280 }
281
282 #[test]
283 fn test_bytes_range_from_range_bounds() {
284 assert_eq!(BytesRange::new(0, None), BytesRange::from(..));
285 assert_eq!(BytesRange::new(10, None), BytesRange::from(10..));
286 assert_eq!(BytesRange::new(0, Some(11)), BytesRange::from(..=10));
287 assert_eq!(BytesRange::new(0, Some(10)), BytesRange::from(..10));
288 assert_eq!(BytesRange::new(10, Some(10)), BytesRange::from(10..20));
289 assert_eq!(BytesRange::new(10, Some(11)), BytesRange::from(10..=20));
290 }
291
292 #[test]
293 fn test_bytes_range_from_str() -> Result<()> {
294 let cases = vec![
295 ("range-start", "bytes=123-", BytesRange::new(123, None)),
296 ("range", "bytes=123-124", BytesRange::new(123, Some(2))),
297 ("one byte", "bytes=0-0", BytesRange::new(0, Some(1))),
298 (
299 "lower case header",
300 "bytes=0-0",
301 BytesRange::new(0, Some(1)),
302 ),
303 ];
304
305 for (name, input, expected) in cases {
306 let actual = input.parse()?;
307
308 assert_eq!(expected, actual, "{name}")
309 }
310
311 Ok(())
312 }
313
314 #[test]
315 fn test_bytes_range_from_str_invalid_end_less_than_start() {
316 let cases = vec!["bytes=100-50", "bytes=10-9", "bytes=1-0"];
317
318 for input in cases {
319 let result: Result<BytesRange> = input.parse();
320 assert!(
321 result.is_err(),
322 "expected error for invalid range {input}, got {result:?}"
323 );
324 }
325 }
326
327 #[allow(clippy::reversed_empty_ranges)]
328 #[test]
329 fn test_bytes_range_from_range_bounds_underflow() {
330 assert_eq!(BytesRange::new(100, Some(0)), BytesRange::from(100..50));
333 assert_eq!(BytesRange::new(10, Some(0)), BytesRange::from(10..=5));
334 assert_eq!(BytesRange::new(5, Some(0)), BytesRange::from(5..0));
335 assert_eq!(BytesRange::new(5, Some(0)), BytesRange::from(5..=0));
336 }
337
338 #[test]
339 fn test_bytes_range_from_range_bounds_u64_max() {
340 assert_eq!(
342 BytesRange::new(0, Some(u64::MAX)),
343 BytesRange::from(..=u64::MAX)
344 );
345 assert_eq!(
346 BytesRange::new(0, Some(u64::MAX)),
347 BytesRange::from(..u64::MAX)
348 );
349 assert_eq!(
350 BytesRange::new(1, Some(u64::MAX.saturating_sub(1))),
351 BytesRange::from(1..=u64::MAX)
352 );
353 assert_eq!(
355 BytesRange::new(u64::MAX, None),
356 BytesRange::from((u64::MAX)..)
357 );
358 }
359
360 #[test]
361 fn test_bytes_range_display_overflow() {
362 let range = BytesRange::new(u64::MAX, Some(2));
364 assert!(std::fmt::write(&mut String::new(), format_args!("{}", range)).is_err());
365 }
366
367 #[test]
368 #[should_panic(expected = "BytesRange::to_range overflow")]
369 fn test_bytes_range_to_range_overflow() {
370 let range = BytesRange::new(u64::MAX, Some(1));
371 let _ = range.to_range();
372 }
373
374 #[test]
375 #[should_panic(expected = "BytesRange::to_range_as_usize")]
376 fn test_bytes_range_to_range_as_usize_overflow() {
377 let range = BytesRange::new(u64::MAX, Some(1));
378 let _ = range.to_range_as_usize();
379 }
380
381 #[test]
382 #[should_panic(expected = "BytesRange::advance overflow")]
383 fn test_bytes_range_advance_offset_overflow() {
384 let mut range = BytesRange::new(u64::MAX, None);
385 range.advance(1);
386 }
387
388 #[test]
389 #[should_panic(expected = "BytesRange::advance underflow")]
390 fn test_bytes_range_advance_size_underflow() {
391 let mut range = BytesRange::new(0, Some(1));
392 range.advance(2);
393 }
394}