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) {
80 self.0 += n;
81 self.1 = self.1.map(|size| size - n);
82 }
83
84 pub fn is_full(&self) -> bool {
88 self.0 == 0 && self.1.is_none()
89 }
90
91 pub fn to_header(&self) -> String {
93 format!("bytes={self}")
94 }
95
96 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 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 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 Ok(BytesRange::new(
163 v[0].parse().map_err(parse_int_error)?,
164 None,
165 ))
166 } else if v[0].is_empty() {
167 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 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 let range = BytesRange::new(0, Some(0));
211 assert!(std::fmt::write(&mut String::new(), format_args!("{}", range)).is_err());
212
213 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}