proxima_primitives/pipe/
header_name.rs1use bytes::Bytes;
2
3#[cfg(feature = "alloc")]
7use crate::pipe::header_list::IntoHeaderBytes;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
20pub enum HeaderName {
21 Host,
22 ContentType,
23 ContentLength,
24 ContentEncoding,
25 TransferEncoding,
26 Connection,
27 Accept,
28 AcceptEncoding,
29 Authorization,
30 UserAgent,
31 CacheControl,
32 Location,
33 Date,
34 Server,
35 Cookie,
36 SetCookie,
37 Traceparent,
38 Tracestate,
39 Baggage,
40 Custom(Bytes),
42}
43
44impl HeaderName {
45 const KNOWN: &'static [(HeaderName, &'static [u8])] = &[
48 (HeaderName::Host, b"host"),
49 (HeaderName::ContentType, b"content-type"),
50 (HeaderName::ContentLength, b"content-length"),
51 (HeaderName::ContentEncoding, b"content-encoding"),
52 (HeaderName::TransferEncoding, b"transfer-encoding"),
53 (HeaderName::Connection, b"connection"),
54 (HeaderName::Accept, b"accept"),
55 (HeaderName::AcceptEncoding, b"accept-encoding"),
56 (HeaderName::Authorization, b"authorization"),
57 (HeaderName::UserAgent, b"user-agent"),
58 (HeaderName::CacheControl, b"cache-control"),
59 (HeaderName::Location, b"location"),
60 (HeaderName::Date, b"date"),
61 (HeaderName::Server, b"server"),
62 (HeaderName::Cookie, b"cookie"),
63 (HeaderName::SetCookie, b"set-cookie"),
64 (HeaderName::Traceparent, b"traceparent"),
65 (HeaderName::Tracestate, b"tracestate"),
66 (HeaderName::Baggage, b"baggage"),
67 ];
68
69 #[must_use]
72 pub fn from_bytes(bytes: &[u8]) -> Self {
73 for (name, canonical) in Self::KNOWN {
74 if bytes.eq_ignore_ascii_case(canonical) {
75 return name.clone();
76 }
77 }
78 Self::Custom(Bytes::copy_from_slice(bytes))
79 }
80
81 #[must_use]
84 pub fn from_wire(bytes: Bytes) -> Self {
85 for (name, canonical) in Self::KNOWN {
86 if bytes.as_ref().eq_ignore_ascii_case(canonical) {
87 return name.clone();
88 }
89 }
90 Self::Custom(bytes)
91 }
92
93 #[must_use]
96 pub fn as_bytes(&self) -> &[u8] {
97 if let Self::Custom(bytes) = self {
98 return bytes.as_ref();
99 }
100 for (name, canonical) in Self::KNOWN {
101 if name == self {
102 return canonical;
103 }
104 }
105 b""
107 }
108
109 #[must_use]
113 pub fn to_bytes(&self) -> Bytes {
114 if let Self::Custom(bytes) = self {
115 return Bytes::clone(bytes);
116 }
117 for (name, canonical) in Self::KNOWN {
118 if name == self {
119 return Bytes::from_static(canonical);
120 }
121 }
122 Bytes::new()
124 }
125
126 #[must_use]
128 pub fn as_str(&self) -> Option<&str> {
129 core::str::from_utf8(self.as_bytes()).ok()
130 }
131}
132
133#[cfg(feature = "alloc")]
134impl IntoHeaderBytes for HeaderName {
135 fn into_header_bytes(self) -> Bytes {
136 self.to_bytes()
137 }
138}
139
140impl From<&[u8]> for HeaderName {
141 fn from(bytes: &[u8]) -> Self {
142 Self::from_bytes(bytes)
143 }
144}
145
146impl From<&str> for HeaderName {
147 fn from(value: &str) -> Self {
148 Self::from_bytes(value.as_bytes())
149 }
150}
151
152impl From<Bytes> for HeaderName {
153 fn from(bytes: Bytes) -> Self {
154 Self::from_wire(bytes)
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn known_names_match_case_insensitively_to_unit_variants() {
164 assert_eq!(
165 HeaderName::from_bytes(b"Content-Length"),
166 HeaderName::ContentLength
167 );
168 assert_eq!(HeaderName::from("CONTENT-TYPE"), HeaderName::ContentType);
169 assert_eq!(HeaderName::from_bytes(b"host"), HeaderName::Host);
170 }
171
172 #[test]
173 fn known_name_to_bytes_is_static_lowercase_no_alloc() {
174 assert_eq!(
176 HeaderName::ContentLength.to_bytes(),
177 Bytes::from_static(b"content-length")
178 );
179 assert_eq!(HeaderName::ContentLength.as_bytes(), b"content-length");
180 }
181
182 #[test]
183 fn from_wire_keeps_unknown_name_zero_copy() {
184 let wire = Bytes::from_static(b"x-custom-thing");
185 let name = HeaderName::from_wire(Bytes::clone(&wire));
186 match name {
187 HeaderName::Custom(bytes) => assert_eq!(bytes.as_ptr(), wire.as_ptr()),
188 other => panic!("expected Custom, got {other:?}"),
189 }
190 }
191
192 #[test]
193 fn into_header_bytes_uses_static_for_known() {
194 let bytes = HeaderName::ContentLength.into_header_bytes();
195 assert_eq!(bytes, Bytes::from_static(b"content-length"));
196 }
197}