Skip to main content

rama_http_headers/common/
accept_ranges.rs

1use rama_core::telemetry::tracing;
2use rama_http_types::HeaderValue;
3
4use crate::util::TryFromValues;
5
6/// `Accept-Ranges` header, defined in [RFC7233](https://datatracker.ietf.org/doc/html/rfc7233#section-2.3)
7///
8/// Unit list: <https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#range-units>.
9///
10/// The `Accept-Ranges` header field allows a server to indicate that it
11/// supports range requests for the target resource.
12///
13/// # ABNF
14///
15/// ```text
16/// Accept-Ranges     = acceptable-ranges
17/// acceptable-ranges = 1#range-unit / \"none\"
18///
19/// # Example values
20/// * `bytes`
21/// * `none`
22/// * `unknown-unit`
23/// ```
24///
25/// # Examples
26///
27/// ```
28/// use rama_http_headers::{AcceptRanges, HeaderMapExt};
29/// use rama_http_types::HeaderMap;
30///
31/// let mut headers = HeaderMap::new();
32///
33/// headers.typed_insert(AcceptRanges::bytes());
34/// ```
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct AcceptRanges(Unit);
37
38derive_header! {
39    AcceptRanges(_),
40    name: ACCEPT_RANGES
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44enum Unit {
45    None,
46    Bytes,
47}
48
49impl From<Unit> for HeaderValue {
50    fn from(value: Unit) -> Self {
51        match value {
52            Unit::None => ACCEPT_RANGES_NONE,
53            Unit::Bytes => ACCEPT_RANGES_BYTES,
54        }
55    }
56}
57
58impl From<&Unit> for HeaderValue {
59    fn from(value: &Unit) -> Self {
60        match value {
61            Unit::None => ACCEPT_RANGES_NONE,
62            Unit::Bytes => ACCEPT_RANGES_BYTES,
63        }
64    }
65}
66
67impl TryFromValues for Unit {
68    fn try_from_values<'i, I>(values: &mut I) -> Result<Self, crate::Error>
69    where
70        Self: Sized,
71        I: Iterator<Item = &'i HeaderValue>,
72    {
73        values
74            .next()
75            .and_then(|val| {
76                let s = val
77                    .to_str()
78                    .inspect_err(|err| {
79                        tracing::trace!("invalid accept-ranges header (unit) value: {err}")
80                    })
81                    .ok()?
82                    .trim();
83                if s.eq_ignore_ascii_case("bytes") {
84                    Some(Self::Bytes)
85                } else if s.eq_ignore_ascii_case("none") {
86                    Some(Self::None)
87                } else {
88                    tracing::trace!("unknown accept-ranges unit value: '{s}'");
89                    None
90                }
91            })
92            .ok_or_else(crate::Error::invalid)
93    }
94}
95
96const ACCEPT_RANGES_NONE: HeaderValue = HeaderValue::from_static("none");
97const ACCEPT_RANGES_BYTES: HeaderValue = HeaderValue::from_static("bytes");
98
99impl AcceptRanges {
100    /// A constructor to easily create the common `Accept-Ranges: bytes` header.
101    #[must_use]
102    pub fn bytes() -> Self {
103        Self(Unit::Bytes)
104    }
105
106    /// Check if the unit is `bytes`.
107    #[must_use]
108    pub fn is_bytes(&self) -> bool {
109        self.0 == Unit::Bytes
110    }
111
112    /// A constructor to easily create the common `Accept-Ranges: none` header.
113    #[must_use]
114    pub fn none() -> Self {
115        Self(Unit::None)
116    }
117
118    /// Check if the unit is `none`.
119    #[must_use]
120    pub fn is_none(&self) -> bool {
121        self.0 == Unit::None
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::super::test_decode;
128    use super::*;
129
130    fn accept_ranges(s: &str) -> AcceptRanges {
131        test_decode(&[s]).unwrap()
132    }
133
134    // bytes
135    #[test]
136    fn bytes_constructor() {
137        assert_eq!(accept_ranges("bytes"), AcceptRanges::bytes());
138        assert_eq!(accept_ranges("Bytes"), AcceptRanges::bytes());
139        assert_eq!(accept_ranges("BYTES"), AcceptRanges::bytes());
140    }
141
142    #[test]
143    fn is_bytes_method_successful_with_bytes_ranges() {
144        assert!(accept_ranges("bytes").is_bytes());
145    }
146
147    #[test]
148    fn is_bytes_method_successful_with_bytes_ranges_by_constructor() {
149        assert!(AcceptRanges::bytes().is_bytes());
150    }
151
152    #[test]
153    fn unknown_range_unit_value_failed() {
154        assert!(test_decode::<AcceptRanges>(&["dummy"]).is_none());
155    }
156
157    // none
158    #[test]
159    fn none_constructor() {
160        assert_eq!(accept_ranges("none"), AcceptRanges::none());
161        assert_eq!(accept_ranges("None"), AcceptRanges::none());
162        assert_eq!(accept_ranges("NONE"), AcceptRanges::none());
163    }
164
165    #[test]
166    fn is_none_method_successful_with_none_ranges() {
167        assert!(accept_ranges("none").is_none());
168    }
169
170    #[test]
171    fn is_none_method_successful_with_none_ranges_by_constructor() {
172        assert!(AcceptRanges::none().is_none());
173    }
174}