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 != UNIT {
33 return Err(ParseHttpRangeOrContentRangeError::InvalidUnit);
34 }
35
36 let (start_str, end_str) = range_str
37 .split_once("-")
38 .ok_or(ParseHttpRangeOrContentRangeError::MalformedRange)?;
39
40 match (start_str.is_empty(), end_str.is_empty()) {
41 (false, false) => {
42 let start = u64_unprefixed_parse(start_str)
43 .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
44 let end = u64_unprefixed_parse(end_str)
45 .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
46
47 let range = OrderedRange::new(start..=end)?;
48 Ok(Self::Range(range))
49 }
50 (false, true) => {
51 let start = u64_unprefixed_parse(start_str)
52 .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
53
54 Ok(Self::StartingPoint(start))
55 }
56 (true, false) => {
57 let suffix = u64_unprefixed_parse(end_str)
58 .map_err(ParseHttpRangeOrContentRangeError::InvalidRangePiece)?;
59
60 Ok(Self::Suffix(suffix))
61 }
62 (true, true) => Err(ParseHttpRangeOrContentRangeError::Malformed),
63 }
64 }
65}
66
67impl From<&HttpRange> for HeaderValue {
68 fn from(value: &HttpRange) -> Self {
69 HeaderValue::from_maybe_shared(value.to_string())
70 .expect("`HttpRange` Display produced non-visible ASCII characters")
71 }
72}
73
74impl TryFrom<&HeaderValue> for HttpRange {
75 type Error = ParseHttpRangeOrContentRangeError;
76 fn try_from(value: &HeaderValue) -> Result<Self, Self::Error> {
77 value
78 .to_str()
79 .map_err(|_| ParseHttpRangeOrContentRangeError::ContainsNonVisibleASCII)?
80 .parse::<Self>()
81 }
82}
83
84#[cfg(feature = "axum")]
85impl<S> axum_core::extract::OptionalFromRequestParts<S> for HttpRange
86where
87 S: Send + Sync,
88{
89 type Rejection = Infallible;
90
91 async fn from_request_parts(
101 parts: &mut http::request::Parts,
102 _state: &S,
103 ) -> Result<Option<Self>, Self::Rejection> {
104 let range = parts
105 .headers
106 .get(http::header::RANGE)
107 .and_then(|range| HttpRange::try_from(range).ok());
108 Ok(range)
109 }
110}
111
112impl Display for HttpRange {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 match self {
115 HttpRange::StartingPoint(start) => write!(f, "{UNIT}={start}-"),
116 HttpRange::Range(range) => write!(f, "{UNIT}={range}"),
117 HttpRange::Suffix(suffix) => write!(f, "{UNIT}=-{suffix}"),
118 }
119 }
120}