use std::sync::{Mutex, PoisonError};
use http::{HeaderMap, HeaderName, HeaderValue};
use topcoat_core::context::{Cx, request_context};
#[derive(Debug, Default)]
pub struct ResponseHeaders {
pending: Mutex<HeaderMap>,
}
impl ResponseHeaders {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn append(&self, name: HeaderName, value: HeaderValue) {
self.lock().append(name, value);
}
pub fn extend(&self, headers: HeaderMap) {
append_all(&mut self.lock(), headers);
}
pub(crate) fn apply(&self, headers: &mut HeaderMap) {
let pending = std::mem::take(&mut *self.lock());
append_all(headers, pending);
}
fn lock(&self) -> std::sync::MutexGuard<'_, HeaderMap> {
self.pending.lock().unwrap_or_else(PoisonError::into_inner)
}
}
fn append_all(into: &mut HeaderMap, from: HeaderMap) {
let mut current = None;
for (name, value) in from {
if let Some(name) = name {
current = Some(name);
}
let name = current
.clone()
.expect("a header map yields a name before its values");
into.append(name, value);
}
}
#[must_use]
#[track_caller]
pub fn response_headers(cx: &Cx) -> &ResponseHeaders {
request_context::<ResponseHeaders>(cx)
}
#[cfg(test)]
mod tests {
use http::header::{CACHE_CONTROL, SET_COOKIE};
use super::*;
fn values(headers: &HeaderMap, name: HeaderName) -> Vec<&str> {
headers
.get_all(name)
.iter()
.map(|value| value.to_str().unwrap())
.collect()
}
#[test]
fn appended_headers_are_added_next_to_existing_values() {
let slot = ResponseHeaders::new();
slot.append(SET_COOKIE, HeaderValue::from_static("a=1"));
slot.append(SET_COOKIE, HeaderValue::from_static("b=2"));
let mut headers = HeaderMap::new();
headers.append(SET_COOKIE, HeaderValue::from_static("c=3"));
slot.apply(&mut headers);
assert_eq!(values(&headers, SET_COOKIE), ["c=3", "a=1", "b=2"]);
}
#[test]
fn extending_queues_every_entry_of_a_map() {
let slot = ResponseHeaders::new();
let mut pending = HeaderMap::new();
pending.append(SET_COOKIE, HeaderValue::from_static("a=1"));
pending.append(SET_COOKIE, HeaderValue::from_static("b=2"));
pending.append(CACHE_CONTROL, HeaderValue::from_static("no-store"));
slot.extend(pending);
let mut headers = HeaderMap::new();
slot.apply(&mut headers);
assert_eq!(values(&headers, SET_COOKIE), ["a=1", "b=2"]);
assert_eq!(values(&headers, CACHE_CONTROL), ["no-store"]);
}
#[test]
fn applying_drains_the_slot() {
let slot = ResponseHeaders::new();
slot.append(CACHE_CONTROL, HeaderValue::from_static("no-store"));
let mut first = HeaderMap::new();
let mut second = HeaderMap::new();
slot.apply(&mut first);
slot.apply(&mut second);
assert_eq!(values(&first, CACHE_CONTROL), ["no-store"]);
assert!(second.is_empty());
}
}