Introduce tower_grpc::metadata::MetadataMap type #92
Conversation
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.
Wow! I haven't reviewed yet, I'm about to look through, but first thoughts: you really took this seriously, this is impressive! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see now that much of the diff is wrapping the http::header
internals. Well done!
What was the decision regarding -bin
headers?
src/metadata_map.rs
Outdated
/// Convert an HTTP HeaderMap to a MetadataMap | ||
pub fn from_headers(mut headers: http::HeaderMap) -> Self { | ||
let mut map = Self::with_capacity(headers.len()); | ||
for (name, values) in headers.drain() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to move these from the headers
into a brand new map? Could this just be MetadataMap { inner: headers }
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
http::HeaderMap
is equal to http::HeaderMap<http::HeaderValue>
. MetadataMap
however has a http::HeaderMap<MetadataValue>
, so when constructing a MetadataMap
the values need to be wrapped individually.
MetadataMap
can't just wrap a http::HeaderMap<http::HeaderValue>
because there are some important methods that return references to those values, which would be unimplementable then (or would require a separate MetadataValueRef
type that wrapped HeaderName&
).
(There is a similar issue for methods that return references to map keys but I worked around that by returning &str
s instead, which is basically what HeaderName
is anyways after construction and you don't need to quickly compare them with standard HTTP header names.)
At least this code just moves all the headers, so runtime is proportional to header count but not size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could still do it, though it would require an unsafe transmute. stdlib relies on this being valid in a number of places. For example, Path::new
where Path(OsStr)
:
pub fn new<S: AsRef<OsStr> + ?Sized>(s: &S) -> &Path {
unsafe { &*(s.as_ref() as *const OsStr as *const Path) }
}
I would use this strategy personally. I think a zero-cost MetadataMap
wrapper is critical here, otherwise switching is not be worth it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool idea! Done.
src/metadata_map.rs
Outdated
} | ||
|
||
/// Convert an HTTP HeaderMap to a MetadataMap | ||
pub fn from_headers(mut headers: http::HeaderMap) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wasn't the point to hide any http
types? Why did you feel this constructor was needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two conversion methods are used in src/request.rs
. Perhaps I should make them pub(crate)
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean... it doesn't seem terrible to have conversions. Being able to easily convert between the two does not violate "hiding".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'll keep them public then.
This PR does not address that issue. This just hides the Differences include:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this 👍 Looking great.
Some thoughts?
- Could you add some some top level docs (even just a few lines) for the
metadata
module. - To avoid cluttering the
metadata
module, maybe we can createmetadata::errors
and export all error types there. Those are less commonly needed and it would make exploringmod metadata
easier.
Additional inline comments.
src/metadata_map.rs
Outdated
} | ||
|
||
/// Convert an HTTP HeaderMap to a MetadataMap | ||
pub fn from_headers(mut headers: http::HeaderMap) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean... it doesn't seem terrible to have conversions. Being able to easily convert between the two does not violate "hiding".
src/metadata_map.rs
Outdated
/// Convert an HTTP HeaderMap to a MetadataMap | ||
pub fn from_headers(mut headers: http::HeaderMap) -> Self { | ||
let mut map = Self::with_capacity(headers.len()); | ||
for (name, values) in headers.drain() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could still do it, though it would require an unsafe transmute. stdlib relies on this being valid in a number of places. For example, Path::new
where Path(OsStr)
:
pub fn new<S: AsRef<OsStr> + ?Sized>(s: &S) -> &Path {
unsafe { &*(s.as_ref() as *const OsStr as *const Path) }
}
I would use this strategy personally. I think a zero-cost MetadataMap
wrapper is critical here, otherwise switching is not be worth it.
4a9e79b
to
3437f90
Compare
This makes the API more self-symmetrical and more similar to HeaderMap.
3437f90
to
e48818f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM modulo that commented out bit that depends on hyperium/http#279.
@seanmonstar you good with this?
@per-gron I pushed an |
|
||
#[inline] | ||
pub(crate) fn from_header_value(header_value: &HeaderValue) -> &Self { | ||
unsafe { &*(header_value as *const HeaderValue as *const Self) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the only way this isn't UB is if MetadataValue
has the #[repr(transparent)]
attribute on it. Otherwise, struct layout is undefined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As long as the struct has a single field and has no other repr on it, it is pretty much guaranteed to have the same layout. I don’t think it has been expressly stated, but std assumes this and so do many other crates.
This technique is basically the only way to define your own DST and has been shown to me by rust team members.
I think we can rely on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@seanmonstar thanks for the link to #[repr(transparent)]
! I was not aware of it. It seems like the perfect thing for this use case, as that is exactly the guarantee that is necessary in this case. I have updated the PR to use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did it realize transparent was stable... that is the best option.
I fixed the last issues. I think this is ready to merge now. |
I'll go ahead and merge this now. |
As per the discussion in #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.
My goal with this PR is to make
MetadataMap
(largely) equivalent toHeaderMap
while not exposing http types.