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