opendal_core/raw/http_util/
bytes_content_range.rs1use std::fmt::Display;
19use std::fmt::Formatter;
20use std::ops::Range;
21use std::ops::RangeInclusive;
22use std::str::FromStr;
23
24use crate::*;
25
26#[allow(clippy::len_without_is_empty)]
49#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
50pub struct BytesContentRange(
51 Option<u64>,
53 Option<u64>,
55 Option<u64>,
57);
58
59impl BytesContentRange {
60 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 pub fn with_size(mut self, size: u64) -> Self {
71 self.2 = Some(size);
72 self
73 }
74
75 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 pub fn size(&self) -> Option<u64> {
90 self.2
91 }
92
93 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 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 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 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 let bcr = BytesContentRange::default().with_range(100, 50);
269 assert_eq!(bcr.len(), Some(0));
270 }
271}