use http::HeaderMap;
use http::StatusCode;
use http::request::Parts;
use crate::extractors::FromRequest;
use crate::extractors::FromRequestParts;
use crate::responder::Responder;
use crate::types::Request;
#[derive(Debug, Clone, Copy)]
#[doc(alias = "range")]
pub struct Range {
pub start: u64,
pub end: u64,
}
#[derive(Debug)]
pub enum RangeError {
Missing,
InvalidFormat,
ParseError,
}
impl Responder for RangeError {
fn into_response(self) -> crate::types::Response {
match self {
RangeError::Missing => {
(StatusCode::RANGE_NOT_SATISFIABLE, "Missing Range header").into_response()
}
RangeError::InvalidFormat => (
StatusCode::RANGE_NOT_SATISFIABLE,
"Invalid Range format. Expected: bytes=start-end",
)
.into_response(),
RangeError::ParseError => (
StatusCode::RANGE_NOT_SATISFIABLE,
"Failed to parse numeric values from Range",
)
.into_response(),
}
}
}
impl Range {
pub fn from_headers(headers: &HeaderMap) -> Result<Option<Self>, RangeError> {
let value = match headers.get("range") {
Some(v) => v.to_str().map_err(|_| RangeError::InvalidFormat)?,
None => return Ok(None),
};
if !value.starts_with("bytes=") {
return Err(RangeError::InvalidFormat);
}
let range = &value["bytes=".len()..];
let mut parts = range.splitn(2, '-');
let start_str = parts.next().ok_or(RangeError::InvalidFormat)?;
let end_str = parts.next().ok_or(RangeError::InvalidFormat)?;
let start = start_str
.parse::<u64>()
.map_err(|_| RangeError::ParseError)?;
let end = if end_str.is_empty() {
0
} else {
end_str.parse::<u64>().map_err(|_| RangeError::ParseError)?
};
Ok(Some(Self { start, end }))
}
}
impl<'a> FromRequest<'a> for Option<Range> {
type Error = RangeError;
fn from_request(
req: &'a mut Request,
) -> impl core::future::Future<Output = Result<Self, Self::Error>> + Send + 'a {
futures_util::future::ready(Range::from_headers(req.headers()))
}
}
impl<'a> FromRequestParts<'a> for Option<Range> {
type Error = RangeError;
fn from_request_parts(
parts: &'a mut Parts,
) -> impl core::future::Future<Output = Result<Self, Self::Error>> + Send + 'a {
futures_util::future::ready(Range::from_headers(&parts.headers))
}
}