gloo_net/http/
headers.rs

1use gloo_utils::iter::UncheckedIter;
2use js_sys::{Array, Map};
3use std::fmt;
4use wasm_bindgen::{JsCast, UnwrapThrowExt};
5
6// I experimented with using `js_sys::Object` for the headers, since this object is marked
7// experimental in MDN. However it's in the fetch spec, and it's necessary for appending headers.
8/// A wrapper around `web_sys::Headers`.
9pub struct Headers {
10    raw: web_sys::Headers,
11}
12
13impl Default for Headers {
14    fn default() -> Self {
15        Self::new()
16    }
17}
18
19impl Headers {
20    /// Create a new empty headers object.
21    pub fn new() -> Self {
22        // pretty sure this will never throw.
23        Self {
24            raw: web_sys::Headers::new().unwrap_throw(),
25        }
26    }
27
28    /// Build [Headers] from [web_sys::Headers].
29    pub fn from_raw(raw: web_sys::Headers) -> Self {
30        Self { raw }
31    }
32
33    /// Covert [Headers] to [web_sys::Headers].
34    pub fn into_raw(self) -> web_sys::Headers {
35        self.raw
36    }
37
38    /// This method appends a new value onto an existing header, or adds the header if it does not
39    /// already exist.
40    pub fn append(&self, name: &str, value: &str) {
41        // XXX Can this throw? WEBIDL says yes, my experiments with forbidden headers and MDN say
42        // no.
43        self.raw.append(name, value).unwrap_throw()
44    }
45
46    /// Deletes a header if it is present.
47    pub fn delete(&self, name: &str) {
48        self.raw.delete(name).unwrap_throw()
49    }
50
51    /// Gets a header if it is present.
52    pub fn get(&self, name: &str) -> Option<String> {
53        self.raw.get(name).unwrap_throw()
54    }
55
56    /// Whether a header with the given name exists.
57    pub fn has(&self, name: &str) -> bool {
58        self.raw.has(name).unwrap_throw()
59    }
60
61    /// Overwrites a header with the given name.
62    pub fn set(&self, name: &str, value: &str) {
63        self.raw.set(name, value).unwrap_throw()
64    }
65
66    /// Iterate over (header name, header value) pairs.
67    pub fn entries(&self) -> impl Iterator<Item = (String, String)> {
68        // Here we cheat and cast to a map even though `self` isn't, because the method names match
69        // and everything works. Is there a better way? Should there be a `MapLike` or
70        // `MapIterator` type in `js_sys`?
71        let fake_map: &Map = self.raw.unchecked_ref();
72        UncheckedIter::from(fake_map.entries()).map(|entry| {
73            let entry: Array = entry.unchecked_into();
74            let key = entry.get(0);
75            let value = entry.get(1);
76            (
77                key.as_string().unwrap_throw(),
78                value.as_string().unwrap_throw(),
79            )
80        })
81    }
82
83    /// Iterate over the names of the headers.
84    pub fn keys(&self) -> impl Iterator<Item = String> {
85        let fake_map: &Map = self.raw.unchecked_ref();
86        UncheckedIter::from(fake_map.keys()).map(|key| key.as_string().unwrap_throw())
87    }
88
89    /// Iterate over the values of the headers.
90    pub fn values(&self) -> impl Iterator<Item = String> {
91        let fake_map: &Map = self.raw.unchecked_ref();
92        UncheckedIter::from(fake_map.values()).map(|v| v.as_string().unwrap_throw())
93    }
94}
95
96impl fmt::Debug for Headers {
97    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98        let mut dbg = f.debug_struct("Headers");
99        for (key, value) in self.entries() {
100            dbg.field(&key, &value);
101        }
102        dbg.finish()
103    }
104}