http_types/headers/
header_name.rs

1use std::borrow::Cow;
2use std::fmt::{self, Debug, Display};
3use std::str::FromStr;
4
5use crate::Error;
6
7/// A header name.
8#[derive(Clone, PartialEq, Eq, Hash)]
9pub struct HeaderName(Cow<'static, str>);
10
11impl HeaderName {
12    /// Create a new `HeaderName` from a Vec of ASCII bytes.
13    ///
14    /// # Error
15    ///
16    /// This function will error if the bytes is not valid ASCII.
17    pub fn from_bytes(mut bytes: Vec<u8>) -> Result<Self, Error> {
18        crate::ensure!(bytes.is_ascii(), "Bytes should be valid ASCII");
19        bytes.make_ascii_lowercase();
20
21        // This is permitted because ASCII is valid UTF-8, and we just checked that.
22        let string = unsafe { String::from_utf8_unchecked(bytes.to_vec()) };
23        Ok(HeaderName(Cow::Owned(string)))
24    }
25
26    /// Create a new `HeaderName` from an ASCII string.
27    ///
28    /// # Error
29    ///
30    /// This function will error if the string is not valid ASCII.
31    pub fn from_string(s: String) -> Result<Self, Error> {
32        Self::from_bytes(s.into_bytes())
33    }
34
35    /// Returns the header name as a `&str`.
36    pub fn as_str(&self) -> &'_ str {
37        &self.0
38    }
39
40    /// Converts a vector of bytes to a `HeaderName` without checking that the string contains
41    /// valid ASCII.
42    ///
43    /// # Safety
44    ///
45    /// This function is unsafe because it does not check that the bytes passed to it are valid
46    /// ASCII. If this constraint is violated, it may cause memory
47    /// unsafety issues with future users of the HeaderName, as the rest of the library assumes
48    /// that Strings are valid ASCII.
49    pub unsafe fn from_bytes_unchecked(mut bytes: Vec<u8>) -> Self {
50        bytes.make_ascii_lowercase();
51        let string = String::from_utf8_unchecked(bytes);
52        HeaderName(Cow::Owned(string))
53    }
54
55    /// Converts a string assumed to lowercase into a `HeaderName`
56    pub(crate) const fn from_lowercase_str(str: &'static str) -> Self {
57        HeaderName(Cow::Borrowed(str))
58    }
59}
60
61impl Debug for HeaderName {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "{:?}", self.0)
64    }
65}
66
67impl Display for HeaderName {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        write!(f, "{}", self.0)
70    }
71}
72
73impl FromStr for HeaderName {
74    type Err = Error;
75
76    /// Create a new `HeaderName`.
77    ///
78    /// This checks it's valid ASCII, and lowercases it.
79    fn from_str(s: &str) -> Result<Self, Self::Err> {
80        crate::ensure!(s.is_ascii(), "String slice should be valid ASCII");
81        Ok(HeaderName(Cow::Owned(s.to_ascii_lowercase())))
82    }
83}
84
85impl From<&HeaderName> for HeaderName {
86    fn from(value: &HeaderName) -> HeaderName {
87        value.clone()
88    }
89}
90
91impl<'a> From<&'a str> for HeaderName {
92    fn from(value: &'a str) -> Self {
93        Self::from_str(value).expect("String slice should be valid ASCII")
94    }
95}
96
97impl PartialEq<str> for HeaderName {
98    fn eq(&self, other: &str) -> bool {
99        match HeaderName::from_str(other) {
100            Err(_) => false,
101            Ok(other) => self == &other,
102        }
103    }
104}
105
106impl<'a> PartialEq<&'a str> for HeaderName {
107    fn eq(&self, other: &&'a str) -> bool {
108        match HeaderName::from_str(other) {
109            Err(_) => false,
110            Ok(other) => self == &other,
111        }
112    }
113}
114
115impl PartialEq<String> for HeaderName {
116    fn eq(&self, other: &String) -> bool {
117        match HeaderName::from_str(other) {
118            Err(_) => false,
119            Ok(other) => self == &other,
120        }
121    }
122}
123
124impl<'a> PartialEq<&String> for HeaderName {
125    fn eq(&self, other: &&String) -> bool {
126        match HeaderName::from_str(other) {
127            Err(_) => false,
128            Ok(other) => self == &other,
129        }
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    #[allow(clippy::eq_op)]
139    fn test_header_name_static_non_static() {
140        let static_header = HeaderName::from_lowercase_str("hello");
141        let non_static_header = HeaderName::from_str("hello").unwrap();
142
143        assert_eq!(&static_header, &non_static_header);
144        assert_eq!(&static_header, &static_header);
145        assert_eq!(&non_static_header, &non_static_header);
146
147        assert_eq!(static_header, non_static_header);
148        assert_eq!(static_header, static_header);
149        assert_eq!(non_static_header, non_static_header);
150    }
151
152    #[test]
153    fn equality() {
154        let static_header = HeaderName::from_lowercase_str("hello");
155        assert_eq!(static_header, "hello");
156        assert_eq!(&static_header, "hello");
157        assert_eq!(static_header, String::from("hello"));
158        assert_eq!(static_header, &String::from("hello"));
159
160        // Must validate regardless of casing.
161        assert_eq!(static_header, &String::from("Hello"));
162    }
163
164    #[test]
165    fn pass_name_by_ref() {
166        let mut res = crate::Response::new(200);
167        res.insert_header(&crate::headers::HOST, "127.0.0.1");
168    }
169
170    #[test]
171    fn test_debug() {
172        let header_name = HeaderName::from_str("hello").unwrap();
173        assert_eq!(format!("{:?}", header_name), "\"hello\"");
174    }
175}