opendal_core/raw/http_util/
bytes_range.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use 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/// BytesRange(offset, size) carries a range of content.
28///
29/// BytesRange implements `ToString` which can be used as `Range` HTTP header directly.
30///
31/// `<unit>` should always be `bytes`.
32///
33/// ```text
34/// Range: bytes=<range-start>-
35/// Range: bytes=<range-start>-<range-end>
36/// ```
37///
38/// # Notes
39///
40/// We don't support tailing read like `Range: bytes=-<range-end>`
41#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
42pub struct BytesRange(
43    /// Offset of the range.
44    u64,
45    /// Size of the range.
46    Option<u64>,
47);
48
49impl BytesRange {
50    /// Create a new `BytesRange`
51    ///
52    /// It better to use `BytesRange::from(1024..2048)` to construct.
53    ///
54    /// # Note
55    ///
56    /// The behavior for `None` and `Some` of `size` is different.
57    ///
58    /// - size=None => `bytes=<offset>-`, read from `<offset>` until the end
59    /// - size=Some(1024) => `bytes=<offset>-<offset + 1024>`, read 1024 bytes starting from the `<offset>`
60    pub fn new(offset: u64, size: Option<u64>) -> Self {
61        BytesRange(offset, size)
62    }
63
64    /// Get offset of BytesRange.
65    pub fn offset(&self) -> u64 {
66        self.0
67    }
68
69    /// Get size of BytesRange.
70    pub fn size(&self) -> Option<u64> {
71        self.1
72    }
73
74    /// Advance the range by `n` bytes.
75    ///
76    /// # Panics
77    ///
78    /// Panic if input `n` is larger than the size of the range.
79    pub fn advance(&mut self, n: u64) {
80        self.0 += n;
81        self.1 = self.1.map(|size| size - n);
82    }
83
84    /// Check if this range is full of this content.
85    ///
86    /// If this range is full, we don't need to specify it in http request.
87    pub fn is_full(&self) -> bool {
88        self.0 == 0 && self.1.is_none()
89    }
90
91    /// Convert bytes range into Range header.
92    pub fn to_header(&self) -> String {
93        format!("bytes={self}")
94    }
95
96    /// Convert bytes range into rust range.
97    pub fn to_range(&self) -> impl RangeBounds<u64> {
98        (
99            Bound::Included(self.0),
100            match self.1 {
101                Some(size) => Bound::Excluded(self.0 + size),
102                None => Bound::Unbounded,
103            },
104        )
105    }
106
107    /// Convert bytes range into rust range with usize.
108    pub fn to_range_as_usize(self) -> impl RangeBounds<usize> {
109        (
110            Bound::Included(self.0 as usize),
111            match self.1 {
112                Some(size) => Bound::Excluded((self.0 + size) as usize),
113                None => Bound::Unbounded,
114            },
115        )
116    }
117}
118
119impl Display for BytesRange {
120    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
121        match self.1 {
122            None => write!(f, "{}-", self.0),
123            // A zero-size range can't be represented as a valid HTTP Range.
124            Some(0) => Err(std::fmt::Error),
125            Some(size) => write!(f, "{}-{}", self.0, self.0 + size - 1),
126        }
127    }
128}
129
130impl FromStr for BytesRange {
131    type Err = Error;
132
133    fn from_str(value: &str) -> Result<Self> {
134        let s = value.strip_prefix("bytes=").ok_or_else(|| {
135            Error::new(ErrorKind::Unexpected, "header range is invalid")
136                .with_operation("BytesRange::from_str")
137                .with_context("value", value)
138        })?;
139
140        if s.contains(',') {
141            return Err(Error::new(ErrorKind::Unexpected, "header range is invalid")
142                .with_operation("BytesRange::from_str")
143                .with_context("value", value));
144        }
145
146        let v = s.split('-').collect::<Vec<_>>();
147        if v.len() != 2 {
148            return Err(Error::new(ErrorKind::Unexpected, "header range is invalid")
149                .with_operation("BytesRange::from_str")
150                .with_context("value", value));
151        }
152
153        let parse_int_error = |e: std::num::ParseIntError| {
154            Error::new(ErrorKind::Unexpected, "header range is invalid")
155                .with_operation("BytesRange::from_str")
156                .with_context("value", value)
157                .set_source(e)
158        };
159
160        if v[1].is_empty() {
161            // <range-start>-
162            Ok(BytesRange::new(
163                v[0].parse().map_err(parse_int_error)?,
164                None,
165            ))
166        } else if v[0].is_empty() {
167            // -<suffix-length>
168            Err(Error::new(
169                ErrorKind::Unexpected,
170                "header range with tailing is not supported",
171            )
172            .with_operation("BytesRange::from_str")
173            .with_context("value", value))
174        } else {
175            // <range-start>-<range-end>
176            let start: u64 = v[0].parse().map_err(parse_int_error)?;
177            let end: u64 = v[1].parse().map_err(parse_int_error)?;
178            Ok(BytesRange::new(start, Some(end - start + 1)))
179        }
180    }
181}
182
183impl<T> From<T> for BytesRange
184where
185    T: RangeBounds<u64>,
186{
187    fn from(range: T) -> Self {
188        let offset = match range.start_bound().cloned() {
189            Bound::Included(n) => n,
190            Bound::Excluded(n) => n + 1,
191            Bound::Unbounded => 0,
192        };
193        let size = match range.end_bound().cloned() {
194            Bound::Included(n) => Some(n + 1 - offset),
195            Bound::Excluded(n) => Some(n - offset),
196            Bound::Unbounded => None,
197        };
198
199        BytesRange(offset, size)
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    #[test]
208    fn test_bytes_range_display_zero_size() {
209        // Zero-size at offset 0
210        let range = BytesRange::new(0, Some(0));
211        assert!(std::fmt::write(&mut String::new(), format_args!("{}", range)).is_err());
212
213        // Zero-size at nonzero offset
214        let range = BytesRange::new(5, Some(0));
215        assert!(std::fmt::write(&mut String::new(), format_args!("{}", range)).is_err());
216    }
217
218    #[test]
219    fn test_bytes_range_to_string() {
220        let h = BytesRange::new(0, Some(1024));
221        assert_eq!(h.to_string(), "0-1023");
222
223        let h = BytesRange::new(1024, None);
224        assert_eq!(h.to_string(), "1024-");
225
226        let h = BytesRange::new(1024, Some(1024));
227        assert_eq!(h.to_string(), "1024-2047");
228    }
229
230    #[test]
231    fn test_bytes_range_to_header() {
232        let h = BytesRange::new(0, Some(1024));
233        assert_eq!(h.to_header(), "bytes=0-1023");
234
235        let h = BytesRange::new(1024, None);
236        assert_eq!(h.to_header(), "bytes=1024-");
237
238        let h = BytesRange::new(1024, Some(1024));
239        assert_eq!(h.to_header(), "bytes=1024-2047");
240    }
241
242    #[test]
243    fn test_bytes_range_from_range_bounds() {
244        assert_eq!(BytesRange::new(0, None), BytesRange::from(..));
245        assert_eq!(BytesRange::new(10, None), BytesRange::from(10..));
246        assert_eq!(BytesRange::new(0, Some(11)), BytesRange::from(..=10));
247        assert_eq!(BytesRange::new(0, Some(10)), BytesRange::from(..10));
248        assert_eq!(BytesRange::new(10, Some(10)), BytesRange::from(10..20));
249        assert_eq!(BytesRange::new(10, Some(11)), BytesRange::from(10..=20));
250    }
251
252    #[test]
253    fn test_bytes_range_from_str() -> Result<()> {
254        let cases = vec![
255            ("range-start", "bytes=123-", BytesRange::new(123, None)),
256            ("range", "bytes=123-124", BytesRange::new(123, Some(2))),
257            ("one byte", "bytes=0-0", BytesRange::new(0, Some(1))),
258            (
259                "lower case header",
260                "bytes=0-0",
261                BytesRange::new(0, Some(1)),
262            ),
263        ];
264
265        for (name, input, expected) in cases {
266            let actual = input.parse()?;
267
268            assert_eq!(expected, actual, "{name}")
269        }
270
271        Ok(())
272    }
273}