Skip to content

Commit

Permalink
Introduce tower_grpc::metadata::MetadataMap type (tower-rs#92)
Browse files Browse the repository at this point in the history
* Rename Request::headers to Request::metadata

* Introduce tower_grpc::metadata::MetadataMap type

As per the discussion in tower-rs#90, it wraps http::HeaderMap to abstract
away the fact that tower-grpc is HTTP based. At this time this class
is a pure wrapper that does not actually implement any of the gRPC
metadata specific stuff (such as binary fields), that will come later.

* Address some review comments

* Use unsafe transmutes to make MetadataMap <=> HeaderMap conversion faster

* Address review comments

* Make the interop tests build

* Say why some code is commented out

* Use unsafe transmutes for returning &MetadataKey from MetadataMap

This makes the API more self-symmetrical and more similar to HeaderMap.

* Use newly exported IterMut and ValuesMut from http to resolve TODOs

* Add #[repr(transparent)] annotations to types that rely on that behavior
  • Loading branch information
per-gron committed Dec 5, 2018
1 parent 8918e84 commit ca2ee54
Show file tree
Hide file tree
Showing 8 changed files with 2,693 additions and 16 deletions.
30 changes: 30 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ pub mod generic;

mod body;
mod error;
mod metadata_key;
mod metadata_value;
mod metadata_map;
mod request;
mod response;
mod status;
Expand All @@ -32,6 +35,33 @@ pub use status::{Code, Status};
pub use request::Request;
pub use response::Response;

/// The metadata module contains data structures and utilities for handling
/// gRPC custom metadata.
pub mod metadata {
pub use metadata_key::MetadataKey;
pub use metadata_value::MetadataValue;
pub use metadata_map::MetadataMap;
pub use metadata_map::Iter;
pub use metadata_map::ValueDrain;
pub use metadata_map::Drain;
pub use metadata_map::Keys;
pub use metadata_map::Values;
pub use metadata_map::ValueIter;
pub use metadata_map::GetAll;
pub use metadata_map::Entry;
pub use metadata_map::VacantEntry;
pub use metadata_map::OccupiedEntry;

/// The metadata::errors module contains types for errors that can occur
/// while handling gRPC custom metadata.
pub mod errors {
pub use metadata_key::InvalidMetadataKey;
pub use metadata_value::InvalidMetadataValue;
pub use metadata_value::InvalidMetadataValueBytes;
pub use metadata_value::ToStrError;
}
}

#[cfg(feature = "protobuf")]
pub mod server;

Expand Down
232 changes: 232 additions & 0 deletions src/metadata_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
use bytes::Bytes;
use http;
use http::header::HeaderName;

use std::borrow::Borrow;
use std::error::Error;
use std::fmt;
use std::str::FromStr;

/// Represents a custom metadata field name.
///
/// `MetadataKey` is used as the [`MetadataMap`] key.
///
/// [`HeaderMap`]: struct.HeaderMap.html
#[derive(Clone, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct MetadataKey {
// Note: There are unsafe transmutes that assume that the memory layout
// of MetadataValue is identical to HeaderName
pub(crate) inner: http::header::HeaderName,
}

/// A possible error when converting a `MetadataKey` from another type.
#[derive(Debug)]
pub struct InvalidMetadataKey {
_priv: (),
}

impl MetadataKey {
/// Converts a slice of bytes to a `MetadataKey`.
///
/// This function normalizes the input.
pub fn from_bytes(src: &[u8]) -> Result<MetadataKey, InvalidMetadataKey> {
match HeaderName::from_bytes(src) {
Ok(name) => Ok(MetadataKey { inner: name }),
Err(_) => Err(InvalidMetadataKey::new())
}
}

/// Converts a static string to a `MetadataKey`.
///
/// This function panics when the static string is a invalid metadata key.
///
/// This function requires the static string to only contain lowercase
/// characters, numerals and symbols, as per the HTTP/2.0 specification
/// and header names internal representation within this library.
///
///
/// # Examples
///
/// ```
/// # use tower_grpc::metadata::*;
/// // Parsing a metadata key
/// let CUSTOM_KEY: &'static str = "custom-key";
///
/// let a = MetadataKey::from_bytes(b"custom-key").unwrap();
/// let b = MetadataKey::from_static(CUSTOM_KEY);
/// assert_eq!(a, b);
/// ```
///
/// ```should_panic
/// # use tower_grpc::metadata::*;
/// #
/// // Parsing a metadata key that contains invalid symbols(s):
/// MetadataKey::from_static("content{}{}length"); // This line panics!
///
/// // Parsing a metadata key that contains invalid uppercase characters.
/// let a = MetadataKey::from_static("foobar");
/// let b = MetadataKey::from_static("FOOBAR"); // This line panics!
/// ```
pub fn from_static(src: &'static str) -> MetadataKey {
MetadataKey { inner: HeaderName::from_static(src) }
}

/// Returns a `str` representation of the metadata key.
///
/// The returned string will always be lower case.
#[inline]
pub fn as_str(&self) -> &str {
self.inner.as_str()
}

#[inline]
pub(crate) fn from_header_name(header_name: &HeaderName) -> &Self {
unsafe { &*(header_name as *const HeaderName as *const Self) }
}
}

impl FromStr for MetadataKey {
type Err = InvalidMetadataKey;

fn from_str(s: &str) -> Result<MetadataKey, InvalidMetadataKey> {
MetadataKey::from_bytes(s.as_bytes())
.map_err(|_| InvalidMetadataKey::new())
}
}

impl AsRef<str> for MetadataKey {
fn as_ref(&self) -> &str {
self.as_str()
}
}

impl AsRef<[u8]> for MetadataKey {
fn as_ref(&self) -> &[u8] {
self.as_str().as_bytes()
}
}

impl Borrow<str> for MetadataKey {
fn borrow(&self) -> &str {
self.as_str()
}
}

impl fmt::Debug for MetadataKey {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), fmt)
}
}

impl fmt::Display for MetadataKey {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self.as_str(), fmt)
}
}

impl InvalidMetadataKey {
pub fn new() -> InvalidMetadataKey {
InvalidMetadataKey { _priv: () }
}
}

impl<'a> From<&'a MetadataKey> for MetadataKey {
fn from(src: &'a MetadataKey) -> MetadataKey {
src.clone()
}
}

impl From<MetadataKey> for Bytes {
#[inline]
fn from(name: MetadataKey) -> Bytes {
name.inner.into()
}
}

impl<'a> PartialEq<&'a MetadataKey> for MetadataKey {
#[inline]
fn eq(&self, other: &&'a MetadataKey) -> bool {
*self == **other
}
}


impl<'a> PartialEq<MetadataKey> for &'a MetadataKey {
#[inline]
fn eq(&self, other: &MetadataKey) -> bool {
*other == *self
}
}

impl PartialEq<str> for MetadataKey {
/// Performs a case-insensitive comparison of the string against the header
/// name
///
/// # Examples
///
/// ```
/// # use tower_grpc::metadata::*;
/// let content_length = MetadataKey::from_static("content-length");
///
/// assert_eq!(content_length, "content-length");
/// assert_eq!(content_length, "Content-Length");
/// assert_ne!(content_length, "content length");
/// ```
#[inline]
fn eq(&self, other: &str) -> bool {
self.inner.eq(other)
}
}


impl PartialEq<MetadataKey> for str {
/// Performs a case-insensitive comparison of the string against the header
/// name
///
/// # Examples
///
/// ```
/// # use tower_grpc::metadata::*;
/// let content_length = MetadataKey::from_static("content-length");
///
/// assert_eq!(content_length, "content-length");
/// assert_eq!(content_length, "Content-Length");
/// assert_ne!(content_length, "content length");
/// ```
#[inline]
fn eq(&self, other: &MetadataKey) -> bool {
(*other).inner == *self
}
}

impl<'a> PartialEq<&'a str> for MetadataKey {
/// Performs a case-insensitive comparison of the string against the header
/// name
#[inline]
fn eq(&self, other: &&'a str) -> bool {
*self == **other
}
}


impl<'a> PartialEq<MetadataKey> for &'a str {
/// Performs a case-insensitive comparison of the string against the header
/// name
#[inline]
fn eq(&self, other: &MetadataKey) -> bool {
*other == *self
}
}

impl fmt::Display for InvalidMetadataKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.description().fmt(f)
}
}

impl Error for InvalidMetadataKey {
fn description(&self) -> &str {
"invalid gRPC metadata key name"
}
}

0 comments on commit ca2ee54

Please sign in to comment.