use std::collections::HashMap; use anyhow::anyhow; use base64::Engine as _; use http::header::{ HeaderMap, HeaderName, HeaderValue, IntoIter, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_TYPE, }; use serde::{ser::SerializeMap, Deserialize, Serialize}; const TRAILER_PREFIX: &str = "trailer-"; const CONNECT_PREFIX: &str = "connect-"; const TRAILER_CONNECT_PREFIX: &str = "trailer-connect-"; const NON_METADATA_HEADERS: &[HeaderName] = &[CONTENT_TYPE, CONTENT_ENCODING, ACCEPT_ENCODING]; #[derive(Clone, Debug, Default, Deserialize)] #[serde(try_from = "HashMap>")] pub struct Metadata(pub(crate) HeaderMap); impl Metadata { pub fn from_headers(headers: HeaderMap) -> Self { let mut meta_headers = HeaderMap::with_capacity(headers.len()); // Note: HeaderMap's into_iter returns `None` for repeated header names, // which requires this `retain` nonsense to sort out let mut retain = true; meta_headers.extend(headers.into_iter().flat_map(|(mut maybe_name, val)| { if let Some(name) = &maybe_name { // Reject reserved headers if name.as_str().starts_with(CONNECT_PREFIX) || name.as_str().starts_with(TRAILER_CONNECT_PREFIX) || NON_METADATA_HEADERS.contains(name) { retain = false; } else { retain = true; // Strip 'trailer-' prefix if let Some(suffix) = name.as_str().strip_prefix(TRAILER_PREFIX) { maybe_name = Some(suffix.parse().unwrap()); } } } retain.then_some((maybe_name, val)) })); Self(meta_headers) } pub fn is_empty(&self) -> bool { self.0.is_empty() } pub fn to_map)>>(&self) -> T { let mut entries = Vec::<(String, Vec)>::new(); for (key, val) in &self.0 { let key = key.as_str(); let last_key = entries.last().map(|(key, _)| key); if Some(key) != last_key.map(|k| k.as_str()) { entries.push((key.to_string(), vec![])); } entries .last_mut() .unwrap() .1 .push(val.to_str().unwrap().to_string()); } T::from_iter(entries) } pub fn append( &mut self, key: impl IntoMetadataKey, val: impl Into, ) -> anyhow::Result<()> { let key = key.into_metadata_key()?; key.validate(false) .map_err(|err| anyhow!("invalid key {key:?}: {err}"))?; let val: HeaderValue = val.into().try_into()?; self.0.append(key.0, val); Ok(()) } pub fn append_binary( &mut self, key: impl IntoMetadataKey, val: impl AsRef<[u8]>, ) -> anyhow::Result<()> { let key = key.into_metadata_key()?; key.validate(true) .map_err(|err| anyhow!("invalid key {key:?}: {err}"))?; let val: HeaderValue = base64::engine::general_purpose::STANDARD_NO_PAD .encode(val) .try_into()?; self.0.append(key.0, val); Ok(()) } } impl IntoIterator for Metadata { type Item = (Option, HeaderValue); type IntoIter = IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl Serialize for Metadata { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut map = serializer.serialize_map(Some(self.0.len()))?; for (key, val) in self.0.iter() { let val = val.to_str().map_err(serde::ser::Error::custom)?; map.serialize_entry(key.as_str(), val)?; } map.end() } } impl TryFrom>> for Metadata { type Error = http::Error; fn try_from(headers: HashMap>) -> Result { let mut map = HeaderMap::with_capacity(headers.len()); for (key, vals) in headers { let key: HeaderName = key.parse()?; if key.as_str().starts_with("connect-") { continue; } for val in vals { let val: HeaderValue = val.try_into()?; map.insert(&key, val); } } Ok(Self(map)) } } #[derive(Clone, PartialEq)] pub struct MetadataKey(HeaderName); impl MetadataKey { fn validate(&self, binary: bool) -> Result<(), &'static str> { if self.0.as_str().starts_with("connect-") { return Err("connect- is a reserved prefix"); } match (self.0.as_str().ends_with("-bin"), binary) { (false, false) | (true, true) => Ok(()), (true, false) => Err("regular (ASCII) metadata keys must not end with -bin"), (false, true) => Err("binary metadata keys must end with -bin"), } } } impl std::fmt::Debug for MetadataKey { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Debug::fmt(self.0.as_str(), fmt) } } pub trait IntoMetadataKey { fn into_metadata_key(self) -> anyhow::Result; } impl IntoMetadataKey for HeaderName { fn into_metadata_key(self) -> anyhow::Result { Ok(MetadataKey(self)) } } impl IntoMetadataKey for &str { fn into_metadata_key(self) -> anyhow::Result { Ok(MetadataKey(self.parse()?)) } } impl IntoMetadataKey for String { fn into_metadata_key(self) -> anyhow::Result { Ok(MetadataKey(self.try_into()?)) } }