range_requests/headers/
range.rs1#[cfg(feature = "axum")]
2use std::convert::Infallible;
3use std::{
4 fmt::{self, Display},
5 str::FromStr,
6};
7
8use http::HeaderValue;
9
10use crate::headers::{OrderedRange, ParseHttpRangeOrContentRangeError, UNIT, u64_unprefixed_parse};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum HttpRange {
15 StartingPoint(u64),
16 Range(OrderedRange),
17 Suffix(u64),
18}
19
20impl FromStr for HttpRange {
21 type Err = ParseHttpRangeOrContentRangeError;
22
23 fn from_str(s: &str) -> Result<Self, Self::Err> {
24 let s = s.trim();
25 if s.is_empty() {
26 return Err(ParseHttpRangeOrContentRangeError::Empty);
27 }
28
29 let (unit_str, range_str) = s
30 .split_once("=")
31 .ok_or(ParseHttpRangeOrContentRangeError::Malformed)?;
32 if !unit_str.eq_ignore_ascii_case(UNIT) {
34 return Err(ParseHttpRangeOrContentRangeError::InvalidUnit);
35 }
36
37 let (start_str, end_str) = range_str
38 .split_once("-")
39 .ok_or(ParseHttpRangeOrContentRangeError::MalformedRange)?;
40
41 match (start_str.is_empty(), end_str.is_empty()) {
42 (false, false) => {
43 let start = u64_unprefixed_parse(start_str)
44 .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
45 let end = u64_unprefixed_parse(end_str)
46 .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
47
48 let range = OrderedRange::new(start..=end)?;
49 Ok(Self::Range(range))
50 }
51 (false, true) => {
52 let start = u64_unprefixed_parse(start_str)
53 .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
54
55 Ok(Self::StartingPoint(start))
56 }
57 (true, false) => {
58 let suffix = u64_unprefixed_parse(end_str)
59 .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
60
61 Ok(Self::Suffix(suffix))
62 }
63 (true, true) => Err(ParseHttpRangeOrContentRangeError::Malformed),
64 }
65 }
66}
67
68impl From<&HttpRange> for HeaderValue {
69 fn from(value: &HttpRange) -> Self {
70 HeaderValue::from_maybe_shared(value.to_string())
71 .expect("`HttpRange` Display produced non-visible ASCII characters")
72 }
73}
74
75impl TryFrom<&HeaderValue> for HttpRange {
76 type Error = ParseHttpRangeOrContentRangeError;
77 fn try_from(value: &HeaderValue) -> Result<Self, Self::Error> {
78 value
79 .to_str()
80 .map_err(|_| ParseHttpRangeOrContentRangeError::ContainsNonVisibleASCII)?
81 .parse::<Self>()
82 }
83}
84
85#[cfg(feature = "axum")]
86impl<S> axum_core::extract::OptionalFromRequestParts<S> for HttpRange
87where
88 S: Send + Sync,
89{
90 type Rejection = Infallible;
91
92 async fn from_request_parts(
103 parts: &mut http::request::Parts,
104 _state: &S,
105 ) -> Result<Option<Self>, Self::Rejection> {
106 if parts.method != http::Method::GET {
107 return Ok(None);
108 }
109
110 let range = parts
111 .headers
112 .get(http::header::RANGE)
113 .and_then(|range| HttpRange::try_from(range).ok());
114 Ok(range)
115 }
116}
117
118impl Display for HttpRange {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 match self {
121 HttpRange::StartingPoint(start) => write!(f, "{UNIT}={start}-"),
122 HttpRange::Range(range) => write!(f, "{UNIT}={range}"),
123 HttpRange::Suffix(suffix) => write!(f, "{UNIT}=-{suffix}"),
124 }
125 }
126}