use bytes::Bytes;
#[cfg(feature = "alloc")]
use crate::pipe::header_list::IntoHeaderBytes;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HeaderName {
Host,
ContentType,
ContentLength,
ContentEncoding,
TransferEncoding,
Connection,
Accept,
AcceptEncoding,
Authorization,
UserAgent,
CacheControl,
Location,
Date,
Server,
Cookie,
SetCookie,
Traceparent,
Tracestate,
Baggage,
Custom(Bytes),
}
impl HeaderName {
const KNOWN: &'static [(HeaderName, &'static [u8])] = &[
(HeaderName::Host, b"host"),
(HeaderName::ContentType, b"content-type"),
(HeaderName::ContentLength, b"content-length"),
(HeaderName::ContentEncoding, b"content-encoding"),
(HeaderName::TransferEncoding, b"transfer-encoding"),
(HeaderName::Connection, b"connection"),
(HeaderName::Accept, b"accept"),
(HeaderName::AcceptEncoding, b"accept-encoding"),
(HeaderName::Authorization, b"authorization"),
(HeaderName::UserAgent, b"user-agent"),
(HeaderName::CacheControl, b"cache-control"),
(HeaderName::Location, b"location"),
(HeaderName::Date, b"date"),
(HeaderName::Server, b"server"),
(HeaderName::Cookie, b"cookie"),
(HeaderName::SetCookie, b"set-cookie"),
(HeaderName::Traceparent, b"traceparent"),
(HeaderName::Tracestate, b"tracestate"),
(HeaderName::Baggage, b"baggage"),
];
#[must_use]
pub fn from_bytes(bytes: &[u8]) -> Self {
for (name, canonical) in Self::KNOWN {
if bytes.eq_ignore_ascii_case(canonical) {
return name.clone();
}
}
Self::Custom(Bytes::copy_from_slice(bytes))
}
#[must_use]
pub fn from_wire(bytes: Bytes) -> Self {
for (name, canonical) in Self::KNOWN {
if bytes.as_ref().eq_ignore_ascii_case(canonical) {
return name.clone();
}
}
Self::Custom(bytes)
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
if let Self::Custom(bytes) = self {
return bytes.as_ref();
}
for (name, canonical) in Self::KNOWN {
if name == self {
return canonical;
}
}
b""
}
#[must_use]
pub fn to_bytes(&self) -> Bytes {
if let Self::Custom(bytes) = self {
return Bytes::clone(bytes);
}
for (name, canonical) in Self::KNOWN {
if name == self {
return Bytes::from_static(canonical);
}
}
Bytes::new()
}
#[must_use]
pub fn as_str(&self) -> Option<&str> {
core::str::from_utf8(self.as_bytes()).ok()
}
}
#[cfg(feature = "alloc")]
impl IntoHeaderBytes for HeaderName {
fn into_header_bytes(self) -> Bytes {
self.to_bytes()
}
}
impl From<&[u8]> for HeaderName {
fn from(bytes: &[u8]) -> Self {
Self::from_bytes(bytes)
}
}
impl From<&str> for HeaderName {
fn from(value: &str) -> Self {
Self::from_bytes(value.as_bytes())
}
}
impl From<Bytes> for HeaderName {
fn from(bytes: Bytes) -> Self {
Self::from_wire(bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn known_names_match_case_insensitively_to_unit_variants() {
assert_eq!(
HeaderName::from_bytes(b"Content-Length"),
HeaderName::ContentLength
);
assert_eq!(HeaderName::from("CONTENT-TYPE"), HeaderName::ContentType);
assert_eq!(HeaderName::from_bytes(b"host"), HeaderName::Host);
}
#[test]
fn known_name_to_bytes_is_static_lowercase_no_alloc() {
assert_eq!(
HeaderName::ContentLength.to_bytes(),
Bytes::from_static(b"content-length")
);
assert_eq!(HeaderName::ContentLength.as_bytes(), b"content-length");
}
#[test]
fn from_wire_keeps_unknown_name_zero_copy() {
let wire = Bytes::from_static(b"x-custom-thing");
let name = HeaderName::from_wire(Bytes::clone(&wire));
match name {
HeaderName::Custom(bytes) => assert_eq!(bytes.as_ptr(), wire.as_ptr()),
other => panic!("expected Custom, got {other:?}"),
}
}
#[test]
fn into_header_bytes_uses_static_for_known() {
let bytes = HeaderName::ContentLength.into_header_bytes();
assert_eq!(bytes, Bytes::from_static(b"content-length"));
}
}