tonic/metadata/key.rs
1/*
2 *
3 * Copyright 2025 gRPC authors.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 *
23 */
24
25use bytes::Bytes;
26use http::header::HeaderName;
27use std::borrow::Borrow;
28use std::error::Error;
29use std::fmt;
30use std::marker::PhantomData;
31use std::str::FromStr;
32
33use super::encoding::{Ascii, Binary, ValueEncoding};
34
35/// Represents a custom metadata field name.
36///
37/// `MetadataKey` is used as the [`MetadataMap`] key.
38///
39/// [`HeaderMap`]: struct.HeaderMap.html
40/// [`MetadataMap`]: struct.MetadataMap.html
41#[derive(Clone, Eq, PartialEq, Hash)]
42#[repr(transparent)]
43pub struct MetadataKey<VE: ValueEncoding> {
44 // Note: There are unsafe transmutes that assume that the memory layout
45 // of MetadataValue is identical to HeaderName
46 pub(crate) inner: http::header::HeaderName,
47 phantom: PhantomData<VE>,
48}
49
50/// A possible error when converting a `MetadataKey` from another type.
51#[derive(Debug, Default)]
52pub struct InvalidMetadataKey {
53 _priv: (),
54}
55
56/// An ascii metadata key.
57pub type AsciiMetadataKey = MetadataKey<Ascii>;
58/// A binary metadata key.
59pub type BinaryMetadataKey = MetadataKey<Binary>;
60
61impl<VE: ValueEncoding> MetadataKey<VE> {
62 /// Converts a slice of bytes to a `MetadataKey`.
63 ///
64 /// This function normalizes the input.
65 pub fn from_bytes(src: &[u8]) -> Result<Self, InvalidMetadataKey> {
66 match HeaderName::from_bytes(src) {
67 Ok(name) => {
68 if !VE::is_valid_key(name.as_str()) {
69 return Err(InvalidMetadataKey::new());
70 }
71
72 Ok(MetadataKey {
73 inner: name,
74 phantom: PhantomData,
75 })
76 }
77 Err(_) => Err(InvalidMetadataKey::new()),
78 }
79 }
80
81 /// Converts a static string to a `MetadataKey`.
82 ///
83 /// This function panics when the static string is a invalid metadata key.
84 ///
85 /// This function requires the static string to only contain lowercase
86 /// characters, numerals and symbols, as per the HTTP/2.0 specification
87 /// and header names internal representation within this library.
88 ///
89 ///
90 /// # Examples
91 ///
92 /// ```
93 /// # use tonic::metadata::*;
94 /// // Parsing a metadata key
95 /// let CUSTOM_KEY: &'static str = "custom-key";
96 ///
97 /// let a = AsciiMetadataKey::from_bytes(b"custom-key").unwrap();
98 /// let b = AsciiMetadataKey::from_static(CUSTOM_KEY);
99 /// assert_eq!(a, b);
100 /// ```
101 ///
102 /// ```should_panic
103 /// # use tonic::metadata::*;
104 /// // Parsing a metadata key that contains invalid symbols(s):
105 /// AsciiMetadataKey::from_static("content{}{}length"); // This line panics!
106 /// ```
107 ///
108 /// ```should_panic
109 /// # use tonic::metadata::*;
110 /// // Parsing a metadata key that contains invalid uppercase characters.
111 /// let a = AsciiMetadataKey::from_static("foobar");
112 /// let b = AsciiMetadataKey::from_static("FOOBAR"); // This line panics!
113 /// ```
114 ///
115 /// ```should_panic
116 /// # use tonic::metadata::*;
117 /// // Parsing a -bin metadata key as an Ascii key.
118 /// let b = AsciiMetadataKey::from_static("hello-bin"); // This line panics!
119 /// ```
120 ///
121 /// ```should_panic
122 /// # use tonic::metadata::*;
123 /// // Parsing a non-bin metadata key as an Binary key.
124 /// let b = BinaryMetadataKey::from_static("hello"); // This line panics!
125 /// ```
126 pub fn from_static(src: &'static str) -> Self {
127 let name = HeaderName::from_static(src);
128 if !VE::is_valid_key(name.as_str()) {
129 panic!("invalid metadata key")
130 }
131
132 MetadataKey {
133 inner: name,
134 phantom: PhantomData,
135 }
136 }
137
138 /// Returns a `str` representation of the metadata key.
139 ///
140 /// The returned string will always be lower case.
141 #[inline]
142 pub fn as_str(&self) -> &str {
143 self.inner.as_str()
144 }
145
146 /// Converts a HeaderName reference to a MetadataKey. This method assumes
147 /// that the caller has made sure that the header name has the correct
148 /// "-bin" or non-"-bin" suffix, it does not validate its input.
149 #[inline]
150 pub(crate) fn unchecked_from_header_name_ref(header_name: &HeaderName) -> &Self {
151 unsafe { &*(header_name as *const HeaderName as *const Self) }
152 }
153
154 /// Converts a HeaderName reference to a MetadataKey. This method assumes
155 /// that the caller has made sure that the header name has the correct
156 /// "-bin" or non-"-bin" suffix, it does not validate its input.
157 #[inline]
158 pub(crate) fn unchecked_from_header_name(name: HeaderName) -> Self {
159 MetadataKey {
160 inner: name,
161 phantom: PhantomData,
162 }
163 }
164}
165
166impl<VE: ValueEncoding> FromStr for MetadataKey<VE> {
167 type Err = InvalidMetadataKey;
168
169 fn from_str(s: &str) -> Result<Self, InvalidMetadataKey> {
170 MetadataKey::from_bytes(s.as_bytes()).map_err(|_| InvalidMetadataKey::new())
171 }
172}
173
174impl<VE: ValueEncoding> AsRef<str> for MetadataKey<VE> {
175 fn as_ref(&self) -> &str {
176 self.as_str()
177 }
178}
179
180impl<VE: ValueEncoding> AsRef<[u8]> for MetadataKey<VE> {
181 fn as_ref(&self) -> &[u8] {
182 self.as_str().as_bytes()
183 }
184}
185
186impl<VE: ValueEncoding> Borrow<str> for MetadataKey<VE> {
187 fn borrow(&self) -> &str {
188 self.as_str()
189 }
190}
191
192impl<VE: ValueEncoding> fmt::Debug for MetadataKey<VE> {
193 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
194 fmt::Debug::fmt(self.as_str(), fmt)
195 }
196}
197
198impl<VE: ValueEncoding> fmt::Display for MetadataKey<VE> {
199 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
200 fmt::Display::fmt(self.as_str(), fmt)
201 }
202}
203
204impl InvalidMetadataKey {
205 #[doc(hidden)]
206 pub fn new() -> InvalidMetadataKey {
207 Self::default()
208 }
209}
210
211impl<'a, VE: ValueEncoding> From<&'a MetadataKey<VE>> for MetadataKey<VE> {
212 fn from(src: &'a MetadataKey<VE>) -> MetadataKey<VE> {
213 src.clone()
214 }
215}
216
217impl<VE: ValueEncoding> From<MetadataKey<VE>> for Bytes {
218 #[inline]
219 fn from(name: MetadataKey<VE>) -> Bytes {
220 Bytes::copy_from_slice(name.inner.as_ref())
221 }
222}
223
224impl<'a, VE: ValueEncoding> PartialEq<&'a MetadataKey<VE>> for MetadataKey<VE> {
225 #[inline]
226 fn eq(&self, other: &&'a MetadataKey<VE>) -> bool {
227 *self == **other
228 }
229}
230
231impl<VE: ValueEncoding> PartialEq<MetadataKey<VE>> for &MetadataKey<VE> {
232 #[inline]
233 fn eq(&self, other: &MetadataKey<VE>) -> bool {
234 *other == *self
235 }
236}
237
238impl<VE: ValueEncoding> PartialEq<str> for MetadataKey<VE> {
239 /// Performs a case-insensitive comparison of the string against the header
240 /// name
241 ///
242 /// # Examples
243 ///
244 /// ```
245 /// # use tonic::metadata::*;
246 /// let content_length = AsciiMetadataKey::from_static("content-length");
247 ///
248 /// assert_eq!(content_length, "content-length");
249 /// assert_eq!(content_length, "Content-Length");
250 /// assert_ne!(content_length, "content length");
251 /// ```
252 #[inline]
253 fn eq(&self, other: &str) -> bool {
254 self.inner.eq(other)
255 }
256}
257
258impl<VE: ValueEncoding> PartialEq<MetadataKey<VE>> for str {
259 /// Performs a case-insensitive comparison of the string against the header
260 /// name
261 ///
262 /// # Examples
263 ///
264 /// ```
265 /// # use tonic::metadata::*;
266 /// let content_length = AsciiMetadataKey::from_static("content-length");
267 ///
268 /// assert_eq!(content_length, "content-length");
269 /// assert_eq!(content_length, "Content-Length");
270 /// assert_ne!(content_length, "content length");
271 /// ```
272 #[inline]
273 fn eq(&self, other: &MetadataKey<VE>) -> bool {
274 other.inner == *self
275 }
276}
277
278impl<'a, VE: ValueEncoding> PartialEq<&'a str> for MetadataKey<VE> {
279 /// Performs a case-insensitive comparison of the string against the header
280 /// name
281 #[inline]
282 fn eq(&self, other: &&'a str) -> bool {
283 *self == **other
284 }
285}
286
287impl<VE: ValueEncoding> PartialEq<MetadataKey<VE>> for &str {
288 /// Performs a case-insensitive comparison of the string against the header
289 /// name
290 #[inline]
291 fn eq(&self, other: &MetadataKey<VE>) -> bool {
292 *other == *self
293 }
294}
295
296impl fmt::Display for InvalidMetadataKey {
297 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298 f.write_str("invalid gRPC metadata key name")
299 }
300}
301
302impl Error for InvalidMetadataKey {}
303
304#[cfg(test)]
305mod tests {
306 use super::{AsciiMetadataKey, BinaryMetadataKey};
307
308 #[test]
309 fn test_from_bytes_binary() {
310 assert!(BinaryMetadataKey::from_bytes(b"").is_err());
311 assert!(BinaryMetadataKey::from_bytes(b"\xFF").is_err());
312 assert!(BinaryMetadataKey::from_bytes(b"abc").is_err());
313 assert_eq!(
314 BinaryMetadataKey::from_bytes(b"abc-bin").unwrap().as_str(),
315 "abc-bin"
316 );
317 }
318
319 #[test]
320 fn test_from_bytes_ascii() {
321 assert!(AsciiMetadataKey::from_bytes(b"").is_err());
322 assert!(AsciiMetadataKey::from_bytes(b"\xFF").is_err());
323 assert_eq!(
324 AsciiMetadataKey::from_bytes(b"abc").unwrap().as_str(),
325 "abc"
326 );
327 assert!(AsciiMetadataKey::from_bytes(b"abc-bin").is_err());
328 }
329}