use std::borrow::{Borrow, Cow};
use std::fmt;
use indexmap::IndexMap;
use crate::uncased::{Uncased, UncasedStr};
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Header<'h> {
pub name: Uncased<'h>,
pub value: Cow<'h, str>,
}
impl<'h> Header<'h> {
#[inline(always)]
pub fn new<'a: 'h, 'b: 'h, N, V>(name: N, value: V) -> Header<'h>
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'b, str>>,
{
Header {
name: Uncased::new(name),
value: value.into(),
}
}
#[doc(hidden)]
pub const fn is_valid_name(name: &str) -> bool {
const fn is_tchar(b: &u8) -> bool {
b.is_ascii_alphanumeric()
|| matches!(
*b,
b'!' | b'#'
| b'$'
| b'%'
| b'&'
| b'\''
| b'*'
| b'+'
| b'-'
| b'.'
| b'^'
| b'_'
| b'`'
| b'|'
| b'~'
)
}
let mut i = 0;
let bytes = name.as_bytes();
while i < bytes.len() {
if !is_tchar(&bytes[i]) {
return false;
}
i += 1;
}
i > 0
}
#[doc(hidden)]
pub const fn is_valid_value(val: &str, allow_empty: bool) -> bool {
const fn is_valid_start(b: &u8) -> bool {
b.is_ascii_graphic()
}
const fn is_valid_continue(b: &u8) -> bool {
is_valid_start(b) || *b == b' ' || *b == b'\t'
}
let mut i = 0;
let bytes = val.as_bytes();
while i < bytes.len() {
match i {
0 if !is_valid_start(&bytes[i]) => return false,
_ if i > 0 && !is_valid_continue(&bytes[i]) => return false,
_ => { }
};
i += 1;
}
allow_empty || i > 0
}
#[inline(always)]
pub fn name(&self) -> &UncasedStr {
&self.name
}
#[inline(always)]
pub fn value(&self) -> &str {
&self.value
}
}
impl fmt::Display for Header<'_> {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.name, self.value)
}
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct HeaderMap<'h> {
headers: IndexMap<Uncased<'h>, Vec<Cow<'h, str>>>,
}
impl<'h> HeaderMap<'h> {
#[inline(always)]
pub fn new() -> HeaderMap<'h> {
HeaderMap {
headers: IndexMap::new(),
}
}
#[inline]
pub fn contains<N: AsRef<str>>(&self, name: N) -> bool {
self.headers.get(UncasedStr::new(name.as_ref())).is_some()
}
#[inline]
pub fn len(&self) -> usize {
self.headers
.iter()
.flat_map(|(_, values)| values.iter())
.count()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.headers.is_empty()
}
#[inline]
pub fn get(&self, name: &str) -> impl Iterator<Item = &str> {
self.headers
.get(UncasedStr::new(name))
.into_iter()
.flat_map(|values| values.iter().map(|val| val.borrow()))
}
#[inline]
pub fn get_one<'a>(&'a self, name: &str) -> Option<&'a str> {
self.headers.get(UncasedStr::new(name)).and_then(|values| {
if !values.is_empty() {
Some(values[0].borrow())
} else {
None
}
})
}
#[inline(always)]
pub fn replace<'p: 'h, H: Into<Header<'p>>>(&mut self, header: H) -> bool {
let header = header.into();
self.headers
.insert(header.name, vec![header.value])
.is_some()
}
#[inline(always)]
pub fn replace_raw<'a: 'h, 'b: 'h, N, V>(&mut self, name: N, value: V) -> bool
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'b, str>>,
{
self.replace(Header::new(name, value))
}
#[inline(always)]
pub fn replace_all<'n, 'v: 'h, H>(&mut self, name: H, values: Vec<Cow<'v, str>>)
where
'n: 'h,
H: Into<Cow<'n, str>>,
{
self.headers.insert(Uncased::new(name), values);
}
#[inline(always)]
pub fn add<'p: 'h, H: Into<Header<'p>>>(&mut self, header: H) {
let header = header.into();
self.headers
.entry(header.name)
.or_default()
.push(header.value);
}
#[inline(always)]
pub fn add_raw<'a: 'h, 'b: 'h, N, V>(&mut self, name: N, value: V)
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'b, str>>,
{
self.add(Header::new(name, value))
}
#[inline(always)]
pub fn add_all<'n, H>(&mut self, name: H, values: &mut Vec<Cow<'h, str>>)
where
'n: 'h,
H: Into<Cow<'n, str>>,
{
self.headers
.entry(Uncased::new(name))
.or_default()
.append(values)
}
#[inline(always)]
pub fn remove(&mut self, name: &str) {
self.headers.swap_remove(UncasedStr::new(name));
}
#[inline(always)]
pub fn remove_all(&mut self) -> Vec<Header<'h>> {
let old_map = std::mem::replace(self, HeaderMap::new());
old_map.into_iter().collect()
}
pub fn iter(&self) -> impl Iterator<Item = Header<'_>> {
self.headers.iter().flat_map(|(key, values)| {
values
.iter()
.map(move |val| Header::new(key.as_str(), &**val))
})
}
#[doc(hidden)]
#[inline]
pub fn into_iter_raw(self) -> impl Iterator<Item = (Uncased<'h>, Vec<Cow<'h, str>>)> {
self.headers.into_iter()
}
}
impl<'h> IntoIterator for HeaderMap<'h> {
type Item = Header<'h>;
type IntoIter = IntoIter<'h>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
headers: self.headers.into_iter(),
current: None,
}
}
}
pub struct IntoIter<'h> {
headers: indexmap::map::IntoIter<Uncased<'h>, Vec<Cow<'h, str>>>,
current: Option<(Uncased<'h>, std::vec::IntoIter<Cow<'h, str>>)>,
}
impl<'h> Iterator for IntoIter<'h> {
type Item = Header<'h>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some((name, values)) = &mut self.current {
if let Some(value) = values.next() {
return Some(Header {
name: name.clone(),
value,
});
}
}
let (name, values) = self.headers.next()?;
self.current = Some((name, values.into_iter()));
}
}
}
impl From<cookie::Cookie<'_>> for Header<'static> {
fn from(cookie: cookie::Cookie<'_>) -> Header<'static> {
Header::new("Set-Cookie", cookie.encoded().to_string())
}
}
impl From<&cookie::Cookie<'_>> for Header<'static> {
fn from(cookie: &cookie::Cookie<'_>) -> Header<'static> {
Header::new("Set-Cookie", cookie.encoded().to_string())
}
}
#[cfg(test)]
mod tests {
use super::HeaderMap;
#[test]
fn case_insensitive_add_get() {
let mut map = HeaderMap::new();
map.add_raw("content-type", "application/json");
let ct = map.get_one("Content-Type");
assert_eq!(ct, Some("application/json"));
let ct2 = map.get_one("CONTENT-TYPE");
assert_eq!(ct2, Some("application/json"))
}
#[test]
fn case_insensitive_multiadd() {
let mut map = HeaderMap::new();
map.add_raw("x-custom", "a");
map.add_raw("X-Custom", "b");
map.add_raw("x-CUSTOM", "c");
let vals: Vec<_> = map.get("x-CuStOm").collect();
assert_eq!(vals, vec!["a", "b", "c"]);
}
}