Skip to content

Commit 85de81c

Browse files
committed
refactor: Simplify range request code and consolidate tests
Reduce test boilerplate by consolidating 20 unit tests into 7 with identical coverage. Remove redundant integration test already covered by unit tests. Use HeaderValue::from_static, delegate to Display impl, and pass ByteRange directly instead of an intermediate HeaderMap.
1 parent c9e5caa commit 85de81c

4 files changed

Lines changed: 60 additions & 163 deletions

File tree

objectstore-server/src/endpoints/objects.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,21 @@ async fn object_get(
100100

101101
let stream = state.meter_stream(stream, &context);
102102
let metadata_headers = metadata.to_headers("").map_err(ServiceError::from)?;
103+
let is_partial = !content_range.is_full();
103104

104-
let status = if content_range.is_full() {
105-
StatusCode::OK
106-
} else {
105+
let status = if is_partial {
107106
StatusCode::PARTIAL_CONTENT
107+
} else {
108+
StatusCode::OK
108109
};
109110

110111
let mut response = (status, metadata_headers, Body::from_stream(stream)).into_response();
111112
let resp_headers = response.headers_mut();
112-
resp_headers.insert(http::header::ACCEPT_RANGES, "bytes".parse().unwrap());
113-
if !content_range.is_full() {
113+
resp_headers.insert(
114+
http::header::ACCEPT_RANGES,
115+
http::header::HeaderValue::from_static("bytes"),
116+
);
117+
if is_partial {
114118
resp_headers.insert(
115119
http::header::CONTENT_LENGTH,
116120
content_range.len().to_string().parse().unwrap(),

objectstore-server/tests/range_requests.rs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -240,20 +240,3 @@ async fn full_range_returns_200() -> Result<()> {
240240
assert_eq!(body, "Hello, Range Requests!");
241241
Ok(())
242242
}
243-
244-
#[tokio::test]
245-
async fn case_insensitive_range_unit() -> Result<()> {
246-
let (server, key) = setup().await;
247-
let client = reqwest::Client::new();
248-
249-
let resp = client
250-
.get(server.url(&format!("/v1/objects/test/org=1/{key}")))
251-
.header("range", "Bytes=0-4")
252-
.send()
253-
.await?;
254-
255-
assert_eq!(resp.status(), reqwest::StatusCode::PARTIAL_CONTENT);
256-
let body = resp.text().await?;
257-
assert_eq!(body, "Hello");
258-
Ok(())
259-
}

objectstore-service/src/backend/s3_compatible.rs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,13 @@ where
164164
&self,
165165
method: Method,
166166
id: &ObjectId,
167-
extra_headers: Option<reqwest::header::HeaderMap>,
167+
range: Option<ByteRange>,
168168
) -> Result<Option<(Metadata, reqwest::Response)>> {
169169
let object_url = self.object_url(id);
170170

171171
let mut builder = self.request(method, &object_url).await?;
172-
if let Some(headers) = extra_headers {
173-
builder = builder.headers(headers);
172+
if let Some(r) = range {
173+
builder = builder.header(reqwest::header::RANGE, r.to_header_value());
174174
}
175175
let response = builder.send().await.map_err(|cause| Error::Reqwest {
176176
context: "S3: failed to send request".to_string(),
@@ -308,15 +308,7 @@ impl<T: TokenProvider> Backend for S3CompatibleBackend<T> {
308308
async fn get_object(&self, id: &ObjectId, range: Option<ByteRange>) -> Result<GetResponse> {
309309
objectstore_log::debug!("Reading from s3_compatible backend");
310310

311-
let extra_headers = range.map(|r| {
312-
let mut headers = reqwest::header::HeaderMap::new();
313-
headers.insert(reqwest::header::RANGE, r.to_header_value());
314-
headers
315-
});
316-
317-
let Some((metadata, response)) =
318-
self.request_object(Method::GET, id, extra_headers).await?
319-
else {
311+
let Some((metadata, response)) = self.request_object(Method::GET, id, range).await? else {
320312
return Ok(None);
321313
};
322314

objectstore-types/src/range.rs

Lines changed: 47 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ impl ByteRange {
2626
ByteRange::From(s) => format!("bytes={s}-"),
2727
ByteRange::Last(n) => format!("bytes=-{n}"),
2828
};
29-
// SAFETY: the format only contains ASCII digits, hyphens, and the
30-
// literal prefix "bytes=", which are all valid header value bytes.
3129
HeaderValue::from_str(&s).expect("ByteRange always produces a valid header value")
3230
}
3331

@@ -168,10 +166,8 @@ impl ContentRange {
168166
/// The returned value is always valid ASCII and can be inserted directly
169167
/// into an HTTP header map.
170168
pub fn to_header_value(&self) -> HeaderValue {
171-
let s = format!("bytes {}-{}/{}", self.start, self.end, self.total);
172-
// SAFETY: the format only contains ASCII digits, spaces, hyphens,
173-
// and slashes, which are all valid header value bytes.
174-
HeaderValue::from_str(&s).expect("ContentRange always produces a valid header value")
169+
HeaderValue::from_str(&self.to_string())
170+
.expect("ContentRange always produces a valid header value")
175171
}
176172
}
177173

@@ -212,33 +208,14 @@ mod tests {
212208
use super::*;
213209

214210
#[test]
215-
fn parse_from_to() {
211+
fn parse_valid_ranges() {
216212
assert_eq!(
217213
ByteRange::try_from("bytes=0-499"),
218214
Ok(ByteRange::Inclusive(0, 499))
219215
);
220-
}
221-
222-
#[test]
223-
fn parse_from() {
224216
assert_eq!(ByteRange::try_from("bytes=500-"), Ok(ByteRange::From(500)));
225-
}
226-
227-
#[test]
228-
fn parse_suffix() {
229217
assert_eq!(ByteRange::try_from("bytes=-100"), Ok(ByteRange::Last(100)));
230-
}
231-
232-
#[test]
233-
fn parse_rejects_multi_range() {
234-
assert_eq!(
235-
ByteRange::try_from("bytes=0-10, 20-30"),
236-
Err(RangeError::MultiRangeNotSupported)
237-
);
238-
}
239-
240-
#[test]
241-
fn parse_case_insensitive() {
218+
// Case insensitive
242219
assert_eq!(
243220
ByteRange::try_from("Bytes=0-499"),
244221
Ok(ByteRange::Inclusive(0, 499))
@@ -247,86 +224,33 @@ mod tests {
247224
}
248225

249226
#[test]
250-
fn parse_returns_unknown_unit_for_non_bytes() {
251-
assert_eq!(ByteRange::try_from("items=0-10"), Err(RangeError::UnknownUnit));
252-
}
253-
254-
#[test]
255-
fn parse_rejects_inverted_range() {
227+
fn parse_invalid_ranges() {
256228
assert_eq!(
257-
ByteRange::try_from("bytes=500-100"),
258-
Err(RangeError::InvalidRange)
229+
ByteRange::try_from("bytes=0-10, 20-30"),
230+
Err(RangeError::MultiRangeNotSupported)
259231
);
260-
}
261-
262-
#[test]
263-
fn parse_rejects_zero_suffix() {
264-
assert_eq!(ByteRange::try_from("bytes=-0"), Err(RangeError::InvalidRange));
265-
}
266-
267-
#[test]
268-
fn resolve_from_to() {
269-
let range = ByteRange::Inclusive(0, 499).resolve(1000);
270232
assert_eq!(
271-
range,
272-
Some(ContentRange {
273-
start: 0,
274-
end: 499,
275-
total: 1000
276-
})
233+
ByteRange::try_from("items=0-10"),
234+
Err(RangeError::UnknownUnit)
277235
);
278-
}
279-
280-
#[test]
281-
fn resolve_from_to_clamped() {
282-
let range = ByteRange::Inclusive(0, 9999).resolve(500);
283236
assert_eq!(
284-
range,
285-
Some(ContentRange {
286-
start: 0,
287-
end: 499,
288-
total: 500
289-
})
290-
);
291-
}
292-
293-
#[test]
294-
fn resolve_from() {
295-
let range = ByteRange::From(500).resolve(1000);
296-
assert_eq!(
297-
range,
298-
Some(ContentRange {
299-
start: 500,
300-
end: 999,
301-
total: 1000
302-
})
237+
ByteRange::try_from("bytes=500-100"),
238+
Err(RangeError::InvalidRange)
303239
);
304-
}
305-
306-
#[test]
307-
fn resolve_suffix() {
308-
let range = ByteRange::Last(100).resolve(1000);
309240
assert_eq!(
310-
range,
311-
Some(ContentRange {
312-
start: 900,
313-
end: 999,
314-
total: 1000
315-
})
241+
ByteRange::try_from("bytes=-0"),
242+
Err(RangeError::InvalidRange)
316243
);
317244
}
318245

319246
#[test]
320-
fn resolve_suffix_larger_than_total() {
321-
let range = ByteRange::Last(2000).resolve(1000);
322-
assert_eq!(
323-
range,
324-
Some(ContentRange {
325-
start: 0,
326-
end: 999,
327-
total: 1000
328-
})
329-
);
247+
fn resolve_satisfiable() {
248+
let cr = |start, end, total| Some(ContentRange { start, end, total });
249+
assert_eq!(ByteRange::Inclusive(0, 499).resolve(1000), cr(0, 499, 1000));
250+
assert_eq!(ByteRange::Inclusive(0, 9999).resolve(500), cr(0, 499, 500));
251+
assert_eq!(ByteRange::From(500).resolve(1000), cr(500, 999, 1000));
252+
assert_eq!(ByteRange::Last(100).resolve(1000), cr(900, 999, 1000));
253+
assert_eq!(ByteRange::Last(2000).resolve(1000), cr(0, 999, 1000));
330254
}
331255

332256
#[test]
@@ -337,60 +261,54 @@ mod tests {
337261
}
338262

339263
#[test]
340-
fn content_range_full() {
341-
let cr = ContentRange::full(1000);
342-
assert_eq!(cr.start, 0);
343-
assert_eq!(cr.end, 999);
344-
assert_eq!(cr.total, 1000);
345-
assert_eq!(cr.len(), 1000);
346-
assert!(cr.is_full());
347-
assert_eq!(cr.to_header_value(), "bytes 0-999/1000");
348-
}
264+
fn content_range_properties() {
265+
let full = ContentRange::full(1000);
266+
assert_eq!(
267+
full,
268+
ContentRange {
269+
start: 0,
270+
end: 999,
271+
total: 1000
272+
}
273+
);
274+
assert_eq!(full.len(), 1000);
275+
assert!(full.is_full());
349276

350-
#[test]
351-
fn content_range_partial_is_not_full() {
352-
let cr = ContentRange {
277+
let partial = ContentRange {
353278
start: 0,
354279
end: 499,
355280
total: 1000,
356281
};
357-
assert!(!cr.is_full());
358-
assert_eq!(cr.len(), 500);
359-
}
282+
assert_eq!(partial.len(), 500);
283+
assert!(!partial.is_full());
360284

361-
#[test]
362-
fn content_range_full_zero_bytes() {
363-
let cr = ContentRange::full(0);
364-
assert_eq!(cr.len(), 0);
365-
assert!(cr.is_full());
285+
let zero = ContentRange::full(0);
286+
assert_eq!(zero.len(), 0);
287+
assert!(zero.is_full());
366288
}
367289

368290
#[test]
369-
fn byte_range_to_header_value() {
291+
fn header_value_roundtrips() {
370292
assert_eq!(
371293
ByteRange::Inclusive(0, 499).to_header_value(),
372294
"bytes=0-499"
373295
);
374296
assert_eq!(ByteRange::From(500).to_header_value(), "bytes=500-");
375297
assert_eq!(ByteRange::Last(100).to_header_value(), "bytes=-100");
376-
}
377298

378-
#[test]
379-
fn content_range_parse() {
380-
assert_eq!(
381-
ContentRange::parse("bytes 0-499/1234"),
382-
Some(ContentRange {
383-
start: 0,
384-
end: 499,
385-
total: 1234
386-
})
387-
);
299+
let cr = ContentRange {
300+
start: 0,
301+
end: 499,
302+
total: 1234,
303+
};
304+
assert_eq!(cr.to_header_value(), "bytes 0-499/1234");
305+
assert_eq!(ContentRange::parse("bytes 0-499/1234"), Some(cr));
388306
assert_eq!(ContentRange::parse("bytes */1234"), None);
389307
assert_eq!(ContentRange::parse("invalid"), None);
390308
}
391309

392310
#[test]
393-
fn content_range_parse_unsatisfiable_total() {
311+
fn parse_unsatisfiable_total() {
394312
assert_eq!(
395313
ContentRange::parse_unsatisfiable_total("bytes */1234"),
396314
Some(1234)

0 commit comments

Comments
 (0)