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 = start_str
50 .parse()
51 .map_err(|_| ParseHttpRangeOrContentRangeError::MalformedRange)?;
52
53 Ok(Self::StartingPoint(start))
54 }
55 (true, false) => {
56 let suffix = end_str
57 .parse()
58 .map_err(|_| ParseHttpRangeOrContentRangeError::MalformedRange)?;
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("The `HttpRange` Display implementation produces nonvisible 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 = ParseHttpRangeOrContentRangeError;
90
91 async fn from_request_parts(
92 parts: &mut http::request::Parts,
93 _state: &S,
94 ) -> Result<Option<Self>, Self::Rejection> {
95 match parts.headers.get(http::header::RANGE) {
96 Some(range) => {
97 let range = HttpRange::try_from(range)?;
98 Ok(Some(range))
99 }
100 None => Ok(None),
101 }
102 }
103}
104
105impl Display for HttpRange {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 match self {
108 HttpRange::StartingPoint(start) => write!(f, "{UNIT}={start}-"),
109 HttpRange::Range(range) => write!(f, "{UNIT}={range}"),
110 HttpRange::Suffix(suffix) => write!(f, "{UNIT}=-{suffix}"),
111 }
112 }
113}