Skip to main content

lino_rest_api/
headers.rs

1//! A small, case-insensitive header map that keeps insertion order.
2//!
3//! HTTP field names are case-insensitive, but the order in which a response
4//! writes them is worth keeping stable, so that two runs of the same request
5//! produce the same bytes on the wire.
6
7use std::fmt;
8
9/// Header fields, looked up without regard to case and written in the order
10/// they were set.
11#[derive(Debug, Clone, Default, PartialEq, Eq)]
12pub struct Headers {
13    entries: Vec<(String, String)>,
14}
15
16impl Headers {
17    /// An empty map.
18    pub fn new() -> Self {
19        Self::default()
20    }
21
22    /// Build a map from pairs, later pairs replacing earlier ones.
23    ///
24    /// # Examples
25    ///
26    /// ```
27    /// use lino_rest_api::headers::Headers;
28    ///
29    /// let headers = Headers::from_pairs([("Content-Type", "text/lino")]);
30    /// assert_eq!(headers.get("content-type"), Some("text/lino"));
31    /// ```
32    pub fn from_pairs<I, N, V>(pairs: I) -> Self
33    where
34        I: IntoIterator<Item = (N, V)>,
35        N: Into<String>,
36        V: Into<String>,
37    {
38        let mut headers = Self::new();
39        for (name, value) in pairs {
40            headers.insert(name, value);
41        }
42        headers
43    }
44
45    /// The value of a field, or [`None`] when it is absent.
46    pub fn get(&self, name: &str) -> Option<&str> {
47        self.entries
48            .iter()
49            .find(|(existing, _)| existing.eq_ignore_ascii_case(name))
50            .map(|(_, value)| value.as_str())
51    }
52
53    /// Whether a field is present.
54    pub fn contains(&self, name: &str) -> bool {
55        self.get(name).is_some()
56    }
57
58    /// Set a field, replacing any existing one and keeping its position.
59    pub fn insert<N: Into<String>, V: Into<String>>(&mut self, name: N, value: V) {
60        let name = name.into();
61        let value = value.into();
62        match self
63            .entries
64            .iter_mut()
65            .find(|(existing, _)| existing.eq_ignore_ascii_case(&name))
66        {
67            Some(entry) => entry.1 = value,
68            None => self.entries.push((name, value)),
69        }
70    }
71
72    /// Set a field only when it is absent, which is how defaults are applied.
73    pub fn insert_missing<N: Into<String>, V: Into<String>>(&mut self, name: N, value: V) {
74        let name = name.into();
75        if !self.contains(&name) {
76            self.insert(name, value);
77        }
78    }
79
80    /// Remove a field, returning its value when there was one.
81    pub fn remove(&mut self, name: &str) -> Option<String> {
82        let index = self
83            .entries
84            .iter()
85            .position(|(existing, _)| existing.eq_ignore_ascii_case(name))?;
86        Some(self.entries.remove(index).1)
87    }
88
89    /// Whether the map carries no field.
90    pub fn is_empty(&self) -> bool {
91        self.entries.is_empty()
92    }
93
94    /// How many fields the map carries.
95    pub fn len(&self) -> usize {
96        self.entries.len()
97    }
98
99    /// The fields, in the order they were set.
100    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
101        self.entries
102            .iter()
103            .map(|(name, value)| (name.as_str(), value.as_str()))
104    }
105
106    /// Merge fields from another map, without overwriting what is already set.
107    pub fn extend_missing(&mut self, other: &Headers) {
108        for (name, value) in other.iter() {
109            self.insert_missing(name.to_string(), value.to_string());
110        }
111    }
112}
113
114impl<'a> IntoIterator for &'a Headers {
115    type Item = (&'a str, &'a str);
116    type IntoIter = Box<dyn Iterator<Item = (&'a str, &'a str)> + 'a>;
117
118    fn into_iter(self) -> Self::IntoIter {
119        Box::new(self.iter())
120    }
121}
122
123impl fmt::Display for Headers {
124    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
125        for (name, value) in self.iter() {
126            writeln!(formatter, "{name}: {value}")?;
127        }
128        Ok(())
129    }
130}
131
132/// Append a field value to a comma-separated list, without repeating it.
133///
134/// This is how `Vary` collects `Accept` and `Origin` (specification section 3).
135///
136/// # Examples
137///
138/// ```
139/// use lino_rest_api::headers::{append_list_value, Headers};
140///
141/// let mut headers = Headers::from_pairs([("Vary", "Accept")]);
142/// append_list_value(&mut headers, "Vary", "Origin");
143/// assert_eq!(headers.get("Vary"), Some("Accept, Origin"));
144/// ```
145pub fn append_list_value(headers: &mut Headers, name: &str, value: &str) {
146    match headers.get(name) {
147        None => headers.insert(name.to_string(), value.to_string()),
148        Some(existing) => {
149            let already = existing
150                .split(',')
151                .any(|part| part.trim().eq_ignore_ascii_case(value));
152            if !already {
153                let merged = format!("{existing}, {value}");
154                headers.insert(name.to_string(), merged);
155            }
156        }
157    }
158}