#![cfg_attr(docsrs, feature(doc_cfg))]
use std::ops::Range;
use bytes::Bytes;
pub mod headers;
use crate::headers::{
content_range::{Bound, HttpContentRange, Unsatisfiable},
range::HttpRange,
};
pub fn serve_file_with_http_range(
body: Bytes,
http_range: Option<HttpRange>,
) -> Result<BodyRange<Bytes>, UnsatisfiableRange> {
let size = u64::try_from(body.len()).expect("we do not support 128bit usize");
let content_range = file_range(size, http_range)?;
let start = usize::try_from(content_range.range.start).expect("u64 doesn't fit usize");
let end = usize::try_from(content_range.range.end).expect("u64 doesn't fit usize");
Ok(BodyRange {
body: body.slice(start..end),
header: content_range.header,
})
}
pub fn file_range(
size: u64,
http_range: Option<HttpRange>,
) -> Result<ContentRange, UnsatisfiableRange> {
let Some(http_range) = http_range else {
return Ok(ContentRange {
header: None,
range: 0..size,
});
};
let range = match http_range {
HttpRange::StartingPoint(start) if start < size => start..size,
HttpRange::Range(range) if range.start() < size => {
range.start()..range.end().saturating_add(1).min(size)
}
HttpRange::Suffix(suffix) if suffix > 0 && size > 0 => size.saturating_sub(suffix)..size,
_ => {
let content_range = HttpContentRange::Unsatisfiable(Unsatisfiable::new(size));
return Err(UnsatisfiableRange(content_range));
}
};
let content_range =
HttpContentRange::Bound(Bound::new(range.start..=range.end - 1, Some(size)).unwrap());
Ok(ContentRange {
header: Some(content_range),
range,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BodyRange<T> {
body: T,
header: Option<HttpContentRange>,
}
impl<T> BodyRange<T> {
pub fn body(&self) -> &T {
&self.body
}
pub fn into_body(self) -> T {
self.body
}
pub fn header(&self) -> Option<HttpContentRange> {
self.header
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContentRange {
header: Option<HttpContentRange>,
range: Range<u64>,
}
impl ContentRange {
pub fn header(&self) -> Option<HttpContentRange> {
self.header
}
pub fn range(&self) -> &Range<u64> {
&self.range
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnsatisfiableRange(HttpContentRange);
impl UnsatisfiableRange {
pub fn header(&self) -> HttpContentRange {
self.0
}
}
#[cfg(feature = "axum")]
mod axum {
use crate::{BodyRange, UnsatisfiableRange};
use axum_core::response::{IntoResponse, Response};
use bytes::Bytes;
use http::{HeaderValue, StatusCode, header::CONTENT_RANGE};
impl IntoResponse for BodyRange<Bytes> {
fn into_response(self) -> Response {
match self.header {
Some(range) => (
StatusCode::PARTIAL_CONTENT,
[(CONTENT_RANGE, HeaderValue::from(&range))],
self.body,
)
.into_response(),
None => (StatusCode::OK, self.body).into_response(),
}
}
}
impl IntoResponse for UnsatisfiableRange {
fn into_response(self) -> Response {
(
StatusCode::RANGE_NOT_SATISFIABLE,
[(CONTENT_RANGE, HeaderValue::from(&self.0))],
)
.into_response()
}
}
}