opendal_core/raw/http_util/
bytes_content_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::Display;
19use std::fmt::Formatter;
20use std::ops::Range;
21use std::ops::RangeInclusive;
22use std::str::FromStr;
23
24use crate::*;
25
26/// BytesContentRange is the content range of bytes.
27///
28/// `<unit>` should always be `bytes`.
29///
30/// ```text
31/// Content-Range: bytes <range-start>-<range-end>/<size>
32/// Content-Range: bytes <range-start>-<range-end>/*
33/// Content-Range: bytes */<size>
34/// ```
35///
36/// # Notes
37///
38/// ## Usage of the default.
39///
40/// `BytesContentRange::default` is not a valid content range.
41/// Please make sure their comes up with `with_range` or `with_size` call.
42///
43/// ## Allow clippy::len_without_is_empty
44///
45/// BytesContentRange implements `len()` but not `is_empty()` because it's useless.
46/// - When BytesContentRange's range is known, it must be non-empty.
47/// - When BytesContentRange's range is no known, we don't know whether it's empty.
48#[allow(clippy::len_without_is_empty)]
49#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
50pub struct BytesContentRange(
51    /// Start position of the range. `None` means unknown.
52    Option<u64>,
53    /// End position of the range. `None` means unknown.
54    Option<u64>,
55    /// Size of the whole content. `None` means unknown.
56    Option<u64>,
57);
58
59impl BytesContentRange {
60    /// Update BytesContentRange with range.
61    ///
62    /// The range is inclusive: `[start..=end]` as described in `content-range`.
63    pub fn with_range(mut self, start: u64, end: u64) -> Self {
64        self.0 = Some(start);
65        self.1 = Some(end);
66        self
67    }
68
69    /// Update BytesContentRange with size.
70    pub fn with_size(mut self, size: u64) -> Self {
71        self.2 = Some(size);
72        self
73    }
74
75    /// Get the length that specified by this BytesContentRange, return `None` if range is not known.
76    pub fn len(&self) -> Option<u64> {
77        if let (Some(start), Some(end)) = (self.0, self.1) {
78            if end < start {
79                Some(0)
80            } else {
81                Some(end - start + 1)
82            }
83        } else {
84            None
85        }
86    }
87
88    /// Get the size of this BytesContentRange, return `None` if size is not known.
89    pub fn size(&self) -> Option<u64> {
90        self.2
91    }
92
93    /// Get the range inclusive of this BytesContentRange, return `None` if range is not known.
94    pub fn range(&self) -> Option<Range<u64>> {
95        if let (Some(start), Some(end)) = (self.0, self.1) {
96            Some(start..end + 1)
97        } else {
98            None
99        }
100    }
101
102    /// Get the range inclusive of this BytesContentRange, return `None` if range is not known.
103    pub fn range_inclusive(&self) -> Option<RangeInclusive<u64>> {
104        if let (Some(start), Some(end)) = (self.0, self.1) {
105            Some(start..=end)
106        } else {
107            None
108        }
109    }
110
111    /// Convert bytes content range into Content-Range header.
112    pub fn to_header(&self) -> String {
113        format!("bytes {self}")
114    }
115}
116
117impl Display for BytesContentRange {
118    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
119        match (self.0, self.1, self.2) {
120            (Some(start), Some(end), Some(size)) => write!(f, "{start}-{end}/{size}"),
121            (Some(start), Some(end), None) => write!(f, "{start}-{end}/*"),
122            (None, None, Some(size)) => write!(f, "*/{size}"),
123            _ => unreachable!("invalid bytes range: {:?}", self),
124        }
125    }
126}
127
128impl FromStr for BytesContentRange {
129    type Err = Error;
130
131    fn from_str(value: &str) -> Result<Self> {
132        let s = value.strip_prefix("bytes ").ok_or_else(|| {
133            Error::new(ErrorKind::Unexpected, "header content range is invalid")
134                .with_operation("BytesContentRange::from_str")
135                .with_context("value", value)
136        })?;
137
138        let parse_int_error = |e: std::num::ParseIntError| {
139            Error::new(ErrorKind::Unexpected, "header content range is invalid")
140                .with_operation("BytesContentRange::from_str")
141                .with_context("value", value)
142                .set_source(e)
143        };
144
145        if let Some(size) = s.strip_prefix("*/") {
146            return Ok(
147                BytesContentRange::default().with_size(size.parse().map_err(parse_int_error)?)
148            );
149        }
150
151        let s: Vec<_> = s.split('/').collect();
152        if s.len() != 2 {
153            return Err(
154                Error::new(ErrorKind::Unexpected, "header content range is invalid")
155                    .with_operation("BytesContentRange::from_str")
156                    .with_context("value", value),
157            );
158        }
159
160        let v: Vec<_> = s[0].split('-').collect();
161        if v.len() != 2 {
162            return Err(
163                Error::new(ErrorKind::Unexpected, "header content range is invalid")
164                    .with_operation("BytesContentRange::from_str")
165                    .with_context("value", value),
166            );
167        }
168        let start: u64 = v[0].parse().map_err(parse_int_error)?;
169        let end: u64 = v[1].parse().map_err(parse_int_error)?;
170        if end < start {
171            return Err(Error::new(
172                ErrorKind::Unexpected,
173                "header content range is invalid: end is less than start",
174            )
175            .with_operation("BytesContentRange::from_str")
176            .with_context("value", value));
177        }
178        let mut bcr = BytesContentRange::default().with_range(start, end);
179
180        // Handle size part first.
181        if s[1] != "*" {
182            bcr = bcr.with_size(s[1].parse().map_err(parse_int_error)?)
183        };
184
185        Ok(bcr)
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn test_bytes_content_range_from_str() -> Result<()> {
195        let cases = vec![
196            (
197                "range start with unknown size",
198                "bytes 123-123/*",
199                BytesContentRange::default().with_range(123, 123),
200            ),
201            (
202                "range start with known size",
203                "bytes 123-123/1024",
204                BytesContentRange::default()
205                    .with_range(123, 123)
206                    .with_size(1024),
207            ),
208            (
209                "only have size",
210                "bytes */1024",
211                BytesContentRange::default().with_size(1024),
212            ),
213        ];
214
215        for (name, input, expected) in cases {
216            let actual = input.parse()?;
217
218            assert_eq!(expected, actual, "{name}")
219        }
220
221        Ok(())
222    }
223
224    #[test]
225    fn test_bytes_content_range_to_string() {
226        let h = BytesContentRange::default().with_size(1024);
227        assert_eq!(h.to_string(), "*/1024");
228
229        let h = BytesContentRange::default().with_range(0, 1023);
230        assert_eq!(h.to_string(), "0-1023/*");
231
232        let h = BytesContentRange::default()
233            .with_range(0, 1023)
234            .with_size(1024);
235        assert_eq!(h.to_string(), "0-1023/1024");
236    }
237
238    #[test]
239    fn test_bytes_content_range_to_header() {
240        let h = BytesContentRange::default().with_size(1024);
241        assert_eq!(h.to_header(), "bytes */1024");
242
243        let h = BytesContentRange::default().with_range(0, 1023);
244        assert_eq!(h.to_header(), "bytes 0-1023/*");
245
246        let h = BytesContentRange::default()
247            .with_range(0, 1023)
248            .with_size(1024);
249        assert_eq!(h.to_header(), "bytes 0-1023/1024");
250    }
251
252    #[test]
253    fn test_bytes_content_range_from_str_invalid_end_less_than_start() {
254        let cases = vec!["bytes 100-50/*", "bytes 10-9/100", "bytes 1-0/100"];
255
256        for input in cases {
257            let result: Result<BytesContentRange> = input.parse();
258            assert!(
259                result.is_err(),
260                "expected error for invalid content range {input}, got {result:?}"
261            );
262        }
263    }
264
265    #[test]
266    fn test_bytes_content_range_len_no_underflow() {
267        // len() should not underflow when end < start.
268        let bcr = BytesContentRange::default().with_range(100, 50);
269        assert_eq!(bcr.len(), Some(0));
270    }
271}