basic body
This commit is contained in:
@@ -17,3 +17,4 @@ tower-service = { git = "https://github.com/tower-rs/tower", branch = "std-futur
|
||||
[dev-dependencies]
|
||||
tokio = "=0.2.0-alpha.1"
|
||||
tonic = { path = "../tonic" }
|
||||
futures-preview = "=0.3.0-alpha.17"
|
||||
|
||||
+81
-9
@@ -1,4 +1,5 @@
|
||||
#![feature(async_await)]
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
@@ -36,7 +37,12 @@ pub fn server(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
// let service_name = service.proto_name.clone();
|
||||
|
||||
let ts = quote! {
|
||||
use tonic::_codegen;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GrpcServer {
|
||||
inner: std::sync::Arc<#s>,
|
||||
}
|
||||
@@ -47,20 +53,86 @@ pub fn server(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
impl tower_service::Service<tonic::Request<()>> for GrpcServer {
|
||||
type Response = tonic::Response<()>;
|
||||
type Error = Status;
|
||||
type Future = tonic::ResponseFuture<'static, Self::Response>;
|
||||
impl _codegen::Service<()> for GrpcServer {
|
||||
type Response = Self;
|
||||
type Error = tonic::error::Never;
|
||||
type Future = _codegen::Ready<Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
|
||||
fn poll_ready(&mut self, _cx: &mut _codegen::Context<'_>) -> _codegen::Poll<Result<(), Self::Error>> {
|
||||
std::task::Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, request: tonic::Request<()>) -> tonic::ResponseFuture<'static, Self::Response> {
|
||||
fn call(&mut self, _: ()) -> Self::Future {
|
||||
_codegen::ok(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl _codegen::Service<_codegen::http::Request<()>> for GrpcServer {
|
||||
type Response = tonic::Response<()>;
|
||||
type Error = tonic::error::Never;
|
||||
type Future = greeter::ResponseFuture;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut _codegen::Context<'_>) -> _codegen::Poll<Result<(), Self::Error>> {
|
||||
Ok(()).into()
|
||||
}
|
||||
|
||||
fn call(&mut self, request: _codegen::http::Request<()>) -> Self::Future {
|
||||
let inner = self.inner.clone();
|
||||
Box::pin(async move {
|
||||
inner.#m_ident(request).await
|
||||
})
|
||||
|
||||
match request.uri().path() {
|
||||
"/helloworld.Greeter/SayHello" => {
|
||||
// let kind = greeter::methods::SayHello(self.inner.clone());
|
||||
// greeter::ResponseFuture { kind: greeter::Kind::SayHello(kind) }
|
||||
self.inner.stream(request).await?;
|
||||
unimplemented!()
|
||||
},
|
||||
_ => unimplemented!("use grpc unimplemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: get actual service name
|
||||
pub mod greeter {
|
||||
use tonic::_codegen::*;
|
||||
|
||||
pub struct ResponseFuture {
|
||||
pub kind: Kind,
|
||||
}
|
||||
|
||||
pub enum Kind {
|
||||
SayHello(methods::SayHello),
|
||||
}
|
||||
|
||||
impl Future for ResponseFuture {
|
||||
type Output = Result<tonic::Response<()>, tonic::error::Never>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub mod methods {
|
||||
use tonic::_codegen::*;
|
||||
|
||||
pub struct SayHello(pub std::sync::Arc<super::super::#s>);
|
||||
|
||||
impl Service<tonic::Request<()>> for SayHello {
|
||||
type Response = tonic::Response<()>;
|
||||
type Error = tonic::Status;
|
||||
type Future = ResponseFuture<Self::Response>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Ok(()).into()
|
||||
}
|
||||
|
||||
fn call(&mut self, request: tonic::Request<()>) -> Self::Future {
|
||||
let inner = self.0.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
inner.#m_ident(request).await
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![feature(async_await)]
|
||||
|
||||
use futures::Stream;
|
||||
use std::time::Duration;
|
||||
use tokio::timer::Delay;
|
||||
use tonic::{Request, Response, Status};
|
||||
@@ -10,7 +11,7 @@ use tonic::{Request, Response, Status};
|
||||
// struct HelloResponse;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct MyGreeter {
|
||||
pub struct MyGreeter {
|
||||
data: String,
|
||||
}
|
||||
|
||||
@@ -30,13 +31,18 @@ impl MyGreeter {
|
||||
|
||||
Ok(Response::new(()))
|
||||
}
|
||||
|
||||
pub async fn server_stream(&self, request: Request<()>) -> Result<impl Stream, Status> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub async fn client_stream(&self, request: Request<impl Stream>) -> Result<(), Status> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grpc() {
|
||||
let svc = MyGreeter::default();
|
||||
let mut server = GrpcServer::from(svc);
|
||||
|
||||
use tower_service::Service;
|
||||
server.call(tonic::Request::new(())).await.unwrap();
|
||||
let mut _server = GrpcServer::from(svc);
|
||||
}
|
||||
|
||||
+12
-1
@@ -7,5 +7,16 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tower-grpc = { git = "https://github.com/tower-rs/tower-grpc", branch = "std-future" }
|
||||
futures-core-preview = "=0.3.0-alpha.17"
|
||||
futures-util-preview = "=0.3.0-alpha.17"
|
||||
tonic-macros = { path = "../tonic-macros" }
|
||||
tracing = "0.1"
|
||||
http = "0.1.14"
|
||||
base64 = "0.10"
|
||||
bytes = "0.4.7"
|
||||
prost = "0.5"
|
||||
percent-encoding = "1.0.1"
|
||||
tower-service = { git = "https://github.com/tower-rs/tower", branch = "std-future" }
|
||||
tokio-codec = "=0.2.0-alpha.1"
|
||||
async-stream = { path = "../../async-stream/async-stream" }
|
||||
http-body = { git = "https://github.com/hyperium/http-body", branch = "std-future" }
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
use crate::{Code, Status};
|
||||
use bytes::{Bytes, IntoBuf};
|
||||
use futures_core::TryStream;
|
||||
use futures_util::{ready, TryStreamExt};
|
||||
use http::HeaderMap;
|
||||
use http_body::Body;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pub type BytesBuf = <Bytes as IntoBuf>::Buf;
|
||||
|
||||
pub struct AsyncBody<S> {
|
||||
inner: S,
|
||||
error: Option<Status>,
|
||||
}
|
||||
|
||||
impl<S> Body for AsyncBody<S>
|
||||
where
|
||||
S: TryStream<Ok = BytesBuf, Error = Status> + Unpin,
|
||||
{
|
||||
type Data = BytesBuf;
|
||||
type Error = Status;
|
||||
|
||||
fn poll_data(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
match ready!(self.inner.try_poll_next_unpin(cx)) {
|
||||
Some(Ok(d)) => Some(Ok(d)).into(),
|
||||
Some(Err(status)) => {
|
||||
self.error = Some(status);
|
||||
None.into()
|
||||
}
|
||||
None => None.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_trailers(&mut self, _cx: &mut Context<'_>) -> Poll<Result<Option<HeaderMap>, Status>> {
|
||||
let status = if let Some(status) = self.error.take() {
|
||||
status
|
||||
} else {
|
||||
Status::new(Code::Ok, "")
|
||||
};
|
||||
|
||||
Poll::Ready(Ok(Some(status.to_header_map()?)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
pub trait Codec {
|
||||
type Encode;
|
||||
type Decode;
|
||||
|
||||
type Encoder;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use std::fmt;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Never {}
|
||||
|
||||
impl fmt::Display for Never {
|
||||
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Never {}
|
||||
+33
-4
@@ -1,16 +1,45 @@
|
||||
pub use tower_grpc::*;
|
||||
#![feature(async_await, type_alias_impl_trait)]
|
||||
|
||||
//! gRPC implementation
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod error;
|
||||
pub mod metadata;
|
||||
|
||||
mod body;
|
||||
mod codec;
|
||||
mod request;
|
||||
mod response;
|
||||
mod server;
|
||||
mod status;
|
||||
|
||||
pub use request::Request;
|
||||
pub use response::Response;
|
||||
pub use status::{Code, Status};
|
||||
pub use tonic_macros::server;
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type ResponseFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T, Status>> + Send + 'a>>;
|
||||
|
||||
pub trait GrpcInnerService<Request> {
|
||||
type Response;
|
||||
type Future: Future<Output = Result<Self::Response, Status>>;
|
||||
|
||||
fn call(self: Arc<Self>, request: Request) -> Self::Future;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
||||
pub mod _codegen {
|
||||
pub use futures_util::future::{ok, Ready};
|
||||
pub use std::future::Future;
|
||||
pub use std::pin::Pin;
|
||||
pub use std::task::{Context, Poll};
|
||||
pub use tower_service::Service;
|
||||
pub type ResponseFuture<T> =
|
||||
self::Pin<Box<dyn self::Future<Output = Result<T, crate::Status>> + Send + 'static>>;
|
||||
|
||||
pub mod http {
|
||||
pub use http::*;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
use bytes::Bytes;
|
||||
use http::header::HeaderValue;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// A possible error when converting a `MetadataValue` from a string or byte
|
||||
/// slice.
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidMetadataValue {
|
||||
_priv: (),
|
||||
}
|
||||
|
||||
mod value_encoding {
|
||||
use super::InvalidMetadataValueBytes;
|
||||
use bytes::Bytes;
|
||||
use http::header::HeaderValue;
|
||||
use std::fmt;
|
||||
|
||||
pub trait Sealed {
|
||||
#[doc(hidden)]
|
||||
fn is_empty(value: &[u8]) -> bool;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn from_bytes(value: &[u8]) -> Result<HeaderValue, InvalidMetadataValueBytes>;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn from_shared(value: Bytes) -> Result<HeaderValue, InvalidMetadataValueBytes>;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn from_static(value: &'static str) -> HeaderValue;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn decode(value: &[u8]) -> Result<Bytes, InvalidMetadataValueBytes>;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn equals(a: &HeaderValue, b: &[u8]) -> bool;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn values_equal(a: &HeaderValue, b: &HeaderValue) -> bool;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn fmt(value: &HeaderValue, f: &mut fmt::Formatter<'_>) -> fmt::Result;
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ValueEncoding: Clone + Eq + PartialEq + Hash + self::value_encoding::Sealed {
|
||||
/// Returns true if the provided key is valid for this ValueEncoding type.
|
||||
/// For example, `Ascii::is_valid_key("a") == true`,
|
||||
/// `Ascii::is_valid_key("a-bin") == false`.
|
||||
fn is_valid_key(key: &str) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum Ascii {}
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum Binary {}
|
||||
|
||||
// ===== impl ValueEncoding =====
|
||||
|
||||
impl self::value_encoding::Sealed for Ascii {
|
||||
fn is_empty(value: &[u8]) -> bool {
|
||||
value.is_empty()
|
||||
}
|
||||
|
||||
fn from_bytes(value: &[u8]) -> Result<HeaderValue, InvalidMetadataValueBytes> {
|
||||
HeaderValue::from_bytes(value).map_err(|_| InvalidMetadataValueBytes::new())
|
||||
}
|
||||
|
||||
fn from_shared(value: Bytes) -> Result<HeaderValue, InvalidMetadataValueBytes> {
|
||||
HeaderValue::from_shared(value).map_err(|_| InvalidMetadataValueBytes::new())
|
||||
}
|
||||
|
||||
fn from_static(value: &'static str) -> HeaderValue {
|
||||
HeaderValue::from_static(value)
|
||||
}
|
||||
|
||||
fn decode(value: &[u8]) -> Result<Bytes, InvalidMetadataValueBytes> {
|
||||
Ok(Bytes::from(value))
|
||||
}
|
||||
|
||||
fn equals(a: &HeaderValue, b: &[u8]) -> bool {
|
||||
a.as_bytes() == b
|
||||
}
|
||||
|
||||
fn values_equal(a: &HeaderValue, b: &HeaderValue) -> bool {
|
||||
a == b
|
||||
}
|
||||
|
||||
fn fmt(value: &HeaderValue, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(value, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueEncoding for Ascii {
|
||||
fn is_valid_key(key: &str) -> bool {
|
||||
!Binary::is_valid_key(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl self::value_encoding::Sealed for Binary {
|
||||
fn is_empty(value: &[u8]) -> bool {
|
||||
for c in value {
|
||||
if *c != b'=' {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn from_bytes(value: &[u8]) -> Result<HeaderValue, InvalidMetadataValueBytes> {
|
||||
let encoded_value: String = base64::encode_config(value, base64::STANDARD_NO_PAD);
|
||||
HeaderValue::from_shared(encoded_value.into()).map_err(|_| InvalidMetadataValueBytes::new())
|
||||
}
|
||||
|
||||
fn from_shared(value: Bytes) -> Result<HeaderValue, InvalidMetadataValueBytes> {
|
||||
Self::from_bytes(value.as_ref())
|
||||
}
|
||||
|
||||
fn from_static(value: &'static str) -> HeaderValue {
|
||||
if !base64::decode(value).is_ok() {
|
||||
panic!("Invalid base64 passed to from_static: {}", value);
|
||||
}
|
||||
unsafe {
|
||||
// Because this is valid base64 this must be a valid HTTP header value,
|
||||
// no need to check again by calling from_shared.
|
||||
HeaderValue::from_shared_unchecked(Bytes::from_static(value.as_ref()))
|
||||
}
|
||||
}
|
||||
|
||||
fn decode(value: &[u8]) -> Result<Bytes, InvalidMetadataValueBytes> {
|
||||
base64::decode(value)
|
||||
.map(|bytes_vec| bytes_vec.into())
|
||||
.map_err(|_| InvalidMetadataValueBytes::new())
|
||||
}
|
||||
|
||||
fn equals(a: &HeaderValue, b: &[u8]) -> bool {
|
||||
if let Ok(decoded) = base64::decode(a.as_bytes()) {
|
||||
decoded == b
|
||||
} else {
|
||||
a.as_bytes() == b
|
||||
}
|
||||
}
|
||||
|
||||
fn values_equal(a: &HeaderValue, b: &HeaderValue) -> bool {
|
||||
let decoded_a = Self::decode(a.as_bytes());
|
||||
let decoded_b = Self::decode(b.as_bytes());
|
||||
if decoded_a.is_ok() && decoded_b.is_ok() {
|
||||
decoded_a.unwrap() == decoded_b.unwrap()
|
||||
} else {
|
||||
!decoded_a.is_ok() && !decoded_b.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt(value: &HeaderValue, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Ok(decoded) = Self::decode(value.as_bytes()) {
|
||||
write!(f, "{:?}", decoded)
|
||||
} else {
|
||||
write!(f, "b[invalid]{:?}", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueEncoding for Binary {
|
||||
fn is_valid_key(key: &str) -> bool {
|
||||
key.ends_with("-bin")
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl InvalidMetadataValue =====
|
||||
|
||||
impl InvalidMetadataValue {
|
||||
pub(crate) fn new() -> Self {
|
||||
InvalidMetadataValue { _priv: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for InvalidMetadataValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.description().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for InvalidMetadataValue {
|
||||
fn description(&self) -> &str {
|
||||
"failed to parse metadata value"
|
||||
}
|
||||
}
|
||||
|
||||
/// A possible error when converting a `MetadataValue` from a string or byte
|
||||
/// slice.
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidMetadataValueBytes(InvalidMetadataValue);
|
||||
|
||||
// ===== impl InvalidMetadataValueBytes =====
|
||||
|
||||
impl InvalidMetadataValueBytes {
|
||||
pub(crate) fn new() -> Self {
|
||||
InvalidMetadataValueBytes(InvalidMetadataValue::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for InvalidMetadataValueBytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for InvalidMetadataValueBytes {
|
||||
fn description(&self) -> &str {
|
||||
self.0.description()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
use bytes::Bytes;
|
||||
use http::header::HeaderName;
|
||||
use std::borrow::Borrow;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::encoding::{Ascii, Binary, ValueEncoding};
|
||||
|
||||
/// 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<VE: ValueEncoding> {
|
||||
// Note: There are unsafe transmutes that assume that the memory layout
|
||||
// of MetadataValue is identical to HeaderName
|
||||
pub(crate) inner: http::header::HeaderName,
|
||||
phantom: PhantomData<VE>,
|
||||
}
|
||||
|
||||
/// A possible error when converting a `MetadataKey` from another type.
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidMetadataKey {
|
||||
_priv: (),
|
||||
}
|
||||
|
||||
pub type AsciiMetadataKey = MetadataKey<Ascii>;
|
||||
pub type BinaryMetadataKey = MetadataKey<Binary>;
|
||||
|
||||
impl<VE: ValueEncoding> MetadataKey<VE> {
|
||||
/// Converts a slice of bytes to a `MetadataKey`.
|
||||
///
|
||||
/// This function normalizes the input.
|
||||
pub fn from_bytes(src: &[u8]) -> Result<Self, InvalidMetadataKey> {
|
||||
match HeaderName::from_bytes(src) {
|
||||
Ok(name) => {
|
||||
if !VE::is_valid_key(name.as_str()) {
|
||||
panic!("invalid metadata key")
|
||||
}
|
||||
|
||||
Ok(MetadataKey {
|
||||
inner: name,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
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 tonic::metadata::*;
|
||||
/// // Parsing a metadata key
|
||||
/// let CUSTOM_KEY: &'static str = "custom-key";
|
||||
///
|
||||
/// let a = AsciiMetadataKey::from_bytes(b"custom-key").unwrap();
|
||||
/// let b = AsciiMetadataKey::from_static(CUSTOM_KEY);
|
||||
/// assert_eq!(a, b);
|
||||
/// ```
|
||||
///
|
||||
/// ```should_panic
|
||||
/// # use tonic::metadata::*;
|
||||
/// // Parsing a metadata key that contains invalid symbols(s):
|
||||
/// AsciiMetadataKey::from_static("content{}{}length"); // This line panics!
|
||||
/// ```
|
||||
///
|
||||
/// ```should_panic
|
||||
/// # use tonic::metadata::*;
|
||||
/// // Parsing a metadata key that contains invalid uppercase characters.
|
||||
/// let a = AsciiMetadataKey::from_static("foobar");
|
||||
/// let b = AsciiMetadataKey::from_static("FOOBAR"); // This line panics!
|
||||
/// ```
|
||||
///
|
||||
/// ```should_panic
|
||||
/// # use tonic::metadata::*;
|
||||
/// // Parsing a -bin metadata key as an Ascii key.
|
||||
/// let b = AsciiMetadataKey::from_static("hello-bin"); // This line panics!
|
||||
/// ```
|
||||
///
|
||||
/// ```should_panic
|
||||
/// # use tonic::metadata::*;
|
||||
/// // Parsing a non-bin metadata key as an Binary key.
|
||||
/// let b = BinaryMetadataKey::from_static("hello"); // This line panics!
|
||||
/// ```
|
||||
pub fn from_static(src: &'static str) -> Self {
|
||||
let name = HeaderName::from_static(src);
|
||||
if !VE::is_valid_key(name.as_str()) {
|
||||
panic!("invalid metadata key")
|
||||
}
|
||||
|
||||
MetadataKey {
|
||||
inner: name,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// Converts a HeaderName reference to a MetadataKey. This method assumes
|
||||
/// that the caller has made sure that the header name has the correct
|
||||
/// "-bin" or non-"-bin" suffix, it does not validate its input.
|
||||
#[inline]
|
||||
pub(crate) fn unchecked_from_header_name_ref(header_name: &HeaderName) -> &Self {
|
||||
unsafe { &*(header_name as *const HeaderName as *const Self) }
|
||||
}
|
||||
|
||||
/// Converts a HeaderName reference to a MetadataKey. This method assumes
|
||||
/// that the caller has made sure that the header name has the correct
|
||||
/// "-bin" or non-"-bin" suffix, it does not validate its input.
|
||||
#[inline]
|
||||
pub(crate) fn unchecked_from_header_name(name: HeaderName) -> Self {
|
||||
MetadataKey {
|
||||
inner: name,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> FromStr for MetadataKey<VE> {
|
||||
type Err = InvalidMetadataKey;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, InvalidMetadataKey> {
|
||||
MetadataKey::from_bytes(s.as_bytes()).map_err(|_| InvalidMetadataKey::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> AsRef<str> for MetadataKey<VE> {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> AsRef<[u8]> for MetadataKey<VE> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.as_str().as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> Borrow<str> for MetadataKey<VE> {
|
||||
fn borrow(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> fmt::Debug for MetadataKey<VE> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(self.as_str(), fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> fmt::Display for MetadataKey<VE> {
|
||||
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, VE: ValueEncoding> From<&'a MetadataKey<VE>> for MetadataKey<VE> {
|
||||
fn from(src: &'a MetadataKey<VE>) -> MetadataKey<VE> {
|
||||
src.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> From<MetadataKey<VE>> for Bytes {
|
||||
#[inline]
|
||||
fn from(name: MetadataKey<VE>) -> Bytes {
|
||||
name.inner.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, VE: ValueEncoding> PartialEq<&'a MetadataKey<VE>> for MetadataKey<VE> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &&'a MetadataKey<VE>) -> bool {
|
||||
*self == **other
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, VE: ValueEncoding> PartialEq<MetadataKey<VE>> for &'a MetadataKey<VE> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &MetadataKey<VE>) -> bool {
|
||||
*other == *self
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> PartialEq<str> for MetadataKey<VE> {
|
||||
/// Performs a case-insensitive comparison of the string against the header
|
||||
/// name
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let content_length = AsciiMetadataKey::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<VE: ValueEncoding> PartialEq<MetadataKey<VE>> for str {
|
||||
/// Performs a case-insensitive comparison of the string against the header
|
||||
/// name
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let content_length = AsciiMetadataKey::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<VE>) -> bool {
|
||||
(*other).inner == *self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, VE: ValueEncoding> PartialEq<&'a str> for MetadataKey<VE> {
|
||||
/// Performs a case-insensitive comparison of the string against the header
|
||||
/// name
|
||||
#[inline]
|
||||
fn eq(&self, other: &&'a str) -> bool {
|
||||
*self == **other
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, VE: ValueEncoding> PartialEq<MetadataKey<VE>> for &'a str {
|
||||
/// Performs a case-insensitive comparison of the string against the header
|
||||
/// name
|
||||
#[inline]
|
||||
fn eq(&self, other: &MetadataKey<VE>) -> 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"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
//! The metadata module contains data structures and utilities for handling
|
||||
//! gRPC custom metadata.
|
||||
|
||||
mod encoding;
|
||||
mod key;
|
||||
mod map;
|
||||
mod value;
|
||||
|
||||
pub use self::encoding::Ascii;
|
||||
pub use self::encoding::Binary;
|
||||
pub use self::key::AsciiMetadataKey;
|
||||
pub use self::key::BinaryMetadataKey;
|
||||
pub use self::key::MetadataKey;
|
||||
pub use self::map::Entry;
|
||||
pub use self::map::GetAll;
|
||||
pub use self::map::Iter;
|
||||
pub use self::map::KeyAndMutValueRef;
|
||||
pub use self::map::KeyAndValueRef;
|
||||
pub use self::map::KeyRef;
|
||||
pub use self::map::Keys;
|
||||
pub use self::map::MetadataMap;
|
||||
pub use self::map::OccupiedEntry;
|
||||
pub use self::map::VacantEntry;
|
||||
pub use self::map::ValueDrain;
|
||||
pub use self::map::ValueIter;
|
||||
pub use self::map::ValueRef;
|
||||
pub use self::map::ValueRefMut;
|
||||
pub use self::map::Values;
|
||||
pub use self::value::AsciiMetadataValue;
|
||||
pub use self::value::BinaryMetadataValue;
|
||||
pub use self::value::MetadataValue;
|
||||
|
||||
/// The metadata::errors module contains types for errors that can occur
|
||||
/// while handling gRPC custom metadata.
|
||||
pub mod errors {
|
||||
pub use super::encoding::InvalidMetadataValue;
|
||||
pub use super::encoding::InvalidMetadataValueBytes;
|
||||
pub use super::key::InvalidMetadataKey;
|
||||
pub use super::value::ToStrError;
|
||||
}
|
||||
@@ -0,0 +1,806 @@
|
||||
use super::encoding::{
|
||||
Ascii, Binary, InvalidMetadataValue, InvalidMetadataValueBytes, ValueEncoding,
|
||||
};
|
||||
use super::key::MetadataKey;
|
||||
|
||||
use bytes::Bytes;
|
||||
use http::header::HeaderValue;
|
||||
use std::error::Error;
|
||||
use std::marker::PhantomData;
|
||||
use std::str::FromStr;
|
||||
use std::{cmp, fmt};
|
||||
|
||||
/// Represents a custom metadata field value.
|
||||
///
|
||||
/// `MetadataValue` is used as the [`MetadataMap`] value.
|
||||
///
|
||||
/// [`HeaderMap`]: struct.HeaderMap.html
|
||||
#[derive(Clone, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct MetadataValue<VE: ValueEncoding> {
|
||||
// Note: There are unsafe transmutes that assume that the memory layout
|
||||
// of MetadataValue is identical to HeaderValue
|
||||
pub(crate) inner: HeaderValue,
|
||||
phantom: PhantomData<VE>,
|
||||
}
|
||||
|
||||
/// A possible error when converting a `MetadataValue` to a string representation.
|
||||
///
|
||||
/// Metadata field values may contain opaque bytes, in which case it is not
|
||||
/// possible to represent the value as a string.
|
||||
#[derive(Debug)]
|
||||
pub struct ToStrError {
|
||||
_priv: (),
|
||||
}
|
||||
|
||||
pub type AsciiMetadataValue = MetadataValue<Ascii>;
|
||||
pub type BinaryMetadataValue = MetadataValue<Binary>;
|
||||
|
||||
impl<VE: ValueEncoding> MetadataValue<VE> {
|
||||
/// Convert a static string to a `MetadataValue`.
|
||||
///
|
||||
/// This function will not perform any copying, however the string is
|
||||
/// checked to ensure that no invalid characters are present.
|
||||
///
|
||||
/// For Ascii values, only visible ASCII characters (32-127) are permitted.
|
||||
/// For Binary values, the string must be valid base64.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if the argument contains invalid metadata value
|
||||
/// characters.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = AsciiMetadataValue::from_static("hello");
|
||||
/// assert_eq!(val, "hello");
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = BinaryMetadataValue::from_static("SGVsbG8hIQ==");
|
||||
/// assert_eq!(val, "Hello!!");
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn from_static(src: &'static str) -> Self {
|
||||
MetadataValue {
|
||||
inner: VE::from_static(src),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to convert a byte slice to a `MetadataValue`.
|
||||
///
|
||||
/// For Ascii metadata values, If the argument contains invalid metadata
|
||||
/// value bytes, an error is returned. Only byte values between 32 and 255
|
||||
/// (inclusive) are permitted, excluding byte 127 (DEL).
|
||||
///
|
||||
/// For Binary metadata values this method cannot fail. See also the Binary
|
||||
/// only version of this method `from_bytes`.
|
||||
///
|
||||
/// This function is intended to be replaced in the future by a `TryFrom`
|
||||
/// implementation once the trait is stabilized in std.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = AsciiMetadataValue::try_from_bytes(b"hello\xfa").unwrap();
|
||||
/// assert_eq!(val, &b"hello\xfa"[..]);
|
||||
/// ```
|
||||
///
|
||||
/// An invalid value
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = AsciiMetadataValue::try_from_bytes(b"\n");
|
||||
/// assert!(val.is_err());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn try_from_bytes(src: &[u8]) -> Result<Self, InvalidMetadataValueBytes> {
|
||||
VE::from_bytes(src).map(|value| MetadataValue {
|
||||
inner: value,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempt to convert a `Bytes` buffer to a `MetadataValue`.
|
||||
///
|
||||
/// For `MetadataValue<Ascii>`, if the argument contains invalid metadata
|
||||
/// value bytes, an error is returned. Only byte values between 32 and 255
|
||||
/// (inclusive) are permitted, excluding byte 127 (DEL).
|
||||
///
|
||||
/// For `MetadataValue<Binary>`, if the argument is not valid base64, an
|
||||
/// error is returned. In use cases where the input is not base64 encoded,
|
||||
/// use `from_bytes`; if the value has to be encoded it's not possible to
|
||||
/// share the memory anyways.
|
||||
///
|
||||
/// This function is intended to be replaced in the future by a `TryFrom`
|
||||
/// implementation once the trait is stabilized in std.
|
||||
#[inline]
|
||||
pub fn from_shared(src: Bytes) -> Result<Self, InvalidMetadataValueBytes> {
|
||||
VE::from_shared(src).map(|value| MetadataValue {
|
||||
inner: value,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert a `Bytes` directly into a `MetadataValue` without validating.
|
||||
/// For MetadataValue<Binary> the provided parameter must be base64
|
||||
/// encoded without padding bytes at the end.
|
||||
///
|
||||
/// This function does NOT validate that illegal bytes are not contained
|
||||
/// within the buffer.
|
||||
#[inline]
|
||||
pub unsafe fn from_shared_unchecked(src: Bytes) -> Self {
|
||||
MetadataValue {
|
||||
inner: HeaderValue::from_shared_unchecked(src),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the `MetadataValue` has a length of zero bytes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = AsciiMetadataValue::from_static("");
|
||||
/// assert!(val.is_empty());
|
||||
///
|
||||
/// let val = AsciiMetadataValue::from_static("hello");
|
||||
/// assert!(!val.is_empty());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
VE::is_empty(self.inner.as_bytes())
|
||||
}
|
||||
|
||||
/// Converts a `MetadataValue` to a Bytes buffer. This method cannot
|
||||
/// fail for Ascii values. For Ascii values, `as_bytes` is more convenient
|
||||
/// to use.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = AsciiMetadataValue::from_static("hello");
|
||||
/// assert_eq!(val.to_bytes().unwrap().as_ref(), b"hello");
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = BinaryMetadataValue::from_bytes(b"hello");
|
||||
/// assert_eq!(val.to_bytes().unwrap().as_ref(), b"hello");
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn to_bytes(&self) -> Result<Bytes, InvalidMetadataValueBytes> {
|
||||
VE::decode(self.inner.as_bytes())
|
||||
}
|
||||
|
||||
/// Mark that the metadata value represents sensitive information.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let mut val = AsciiMetadataValue::from_static("my secret");
|
||||
///
|
||||
/// val.set_sensitive(true);
|
||||
/// assert!(val.is_sensitive());
|
||||
///
|
||||
/// val.set_sensitive(false);
|
||||
/// assert!(!val.is_sensitive());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn set_sensitive(&mut self, val: bool) {
|
||||
self.inner.set_sensitive(val);
|
||||
}
|
||||
|
||||
/// Returns `true` if the value represents sensitive data.
|
||||
///
|
||||
/// Sensitive data could represent passwords or other data that should not
|
||||
/// be stored on disk or in memory. This setting can be used by components
|
||||
/// like caches to avoid storing the value. HPACK encoders must set the
|
||||
/// metadata field to never index when `is_sensitive` returns true.
|
||||
///
|
||||
/// Note that sensitivity is not factored into equality or ordering.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let mut val = AsciiMetadataValue::from_static("my secret");
|
||||
///
|
||||
/// val.set_sensitive(true);
|
||||
/// assert!(val.is_sensitive());
|
||||
///
|
||||
/// val.set_sensitive(false);
|
||||
/// assert!(!val.is_sensitive());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn is_sensitive(&self) -> bool {
|
||||
self.inner.is_sensitive()
|
||||
}
|
||||
|
||||
/// Converts a `MetadataValue` to a byte slice. For Binary values, the
|
||||
/// return value is base64 encoded.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = AsciiMetadataValue::from_static("hello");
|
||||
/// assert_eq!(val.as_encoded_bytes(), b"hello");
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = BinaryMetadataValue::from_bytes(b"Hello!");
|
||||
/// assert_eq!(val.as_encoded_bytes(), b"SGVsbG8h");
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn as_encoded_bytes(&self) -> &[u8] {
|
||||
self.inner.as_bytes()
|
||||
}
|
||||
|
||||
/// Converts a HeaderValue to a MetadataValue. This method assumes that the
|
||||
/// caller has made sure that the value is of the correct Ascii or Binary
|
||||
/// value encoding.
|
||||
#[inline]
|
||||
pub(crate) fn unchecked_from_header_value(value: HeaderValue) -> Self {
|
||||
MetadataValue {
|
||||
inner: value,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a HeaderValue reference to a MetadataValue. This method assumes
|
||||
/// that the caller has made sure that the value is of the correct Ascii or
|
||||
/// Binary value encoding.
|
||||
#[inline]
|
||||
pub(crate) fn unchecked_from_header_value_ref(header_value: &HeaderValue) -> &Self {
|
||||
unsafe { &*(header_value as *const HeaderValue as *const Self) }
|
||||
}
|
||||
|
||||
/// Converts a HeaderValue reference to a MetadataValue. This method assumes
|
||||
/// that the caller has made sure that the value is of the correct Ascii or
|
||||
/// Binary value encoding.
|
||||
#[inline]
|
||||
pub(crate) fn unchecked_from_mut_header_value_ref(header_value: &mut HeaderValue) -> &mut Self {
|
||||
unsafe { &mut *(header_value as *mut HeaderValue as *mut Self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl MetadataValue<Ascii> {
|
||||
/// Attempt to convert a string to a `MetadataValue<Ascii>`.
|
||||
///
|
||||
/// If the argument contains invalid metadata value characters, an error is
|
||||
/// returned. Only visible ASCII characters (32-127) are permitted. Use
|
||||
/// `from_bytes` to create a `MetadataValue` that includes opaque octets
|
||||
/// (128-255).
|
||||
///
|
||||
/// This function is intended to be replaced in the future by a `TryFrom`
|
||||
/// implementation once the trait is stabilized in std.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = AsciiMetadataValue::from_str("hello").unwrap();
|
||||
/// assert_eq!(val, "hello");
|
||||
/// ```
|
||||
///
|
||||
/// An invalid value
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = AsciiMetadataValue::from_str("\n");
|
||||
/// assert!(val.is_err());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn from_str(src: &str) -> Result<Self, InvalidMetadataValue> {
|
||||
HeaderValue::from_str(src)
|
||||
.map(|value| MetadataValue {
|
||||
inner: value,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
.map_err(|_| InvalidMetadataValue::new())
|
||||
}
|
||||
|
||||
/// Converts a MetadataKey into a MetadataValue<Ascii>.
|
||||
///
|
||||
/// Since every valid MetadataKey is a valid MetadataValue this is done
|
||||
/// infallibly.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = AsciiMetadataValue::from_key::<Ascii>("accept".parse().unwrap());
|
||||
/// assert_eq!(val, AsciiMetadataValue::try_from_bytes(b"accept").unwrap());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn from_key<KeyVE: ValueEncoding>(key: MetadataKey<KeyVE>) -> Self {
|
||||
key.into()
|
||||
}
|
||||
|
||||
/// Returns the length of `self`, in bytes.
|
||||
///
|
||||
/// This method is not available for MetadataValue<Binary> because that
|
||||
/// cannot be implemented in constant time, which most people would probably
|
||||
/// expect. To get the length of MetadataValue<Binary>, convert it to a
|
||||
/// Bytes value and measure its length.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = AsciiMetadataValue::from_static("hello");
|
||||
/// assert_eq!(val.len(), 5);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
/// Yields a `&str` slice if the `MetadataValue` only contains visible ASCII
|
||||
/// chars.
|
||||
///
|
||||
/// This function will perform a scan of the metadata value, checking all the
|
||||
/// characters.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = AsciiMetadataValue::from_static("hello");
|
||||
/// assert_eq!(val.to_str().unwrap(), "hello");
|
||||
/// ```
|
||||
pub fn to_str(&self) -> Result<&str, ToStrError> {
|
||||
return self.inner.to_str().map_err(|_| ToStrError::new());
|
||||
}
|
||||
|
||||
/// Converts a `MetadataValue` to a byte slice. For Binary values, use
|
||||
/// `to_bytes`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = AsciiMetadataValue::from_static("hello");
|
||||
/// assert_eq!(val.as_bytes(), b"hello");
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.inner.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl MetadataValue<Binary> {
|
||||
/// Convert a byte slice to a `MetadataValue<Binary>`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tonic::metadata::*;
|
||||
/// let val = BinaryMetadataValue::from_bytes(b"hello\xfa");
|
||||
/// assert_eq!(val, &b"hello\xfa"[..]);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn from_bytes(src: &[u8]) -> Self {
|
||||
// Only the Ascii version of try_from_bytes can fail.
|
||||
Self::try_from_bytes(src).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> AsRef<[u8]> for MetadataValue<VE> {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.inner.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> fmt::Debug for MetadataValue<VE> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
VE::fmt(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<KeyVE: ValueEncoding> From<MetadataKey<KeyVE>> for MetadataValue<Ascii> {
|
||||
#[inline]
|
||||
fn from(h: MetadataKey<KeyVE>) -> MetadataValue<Ascii> {
|
||||
MetadataValue {
|
||||
inner: h.inner.into(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! from_integers {
|
||||
($($name:ident: $t:ident => $max_len:expr),*) => {$(
|
||||
impl From<$t> for MetadataValue<Ascii> {
|
||||
fn from(num: $t) -> MetadataValue<Ascii> {
|
||||
MetadataValue {
|
||||
inner: HeaderValue::from(num),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn $name() {
|
||||
let n: $t = 55;
|
||||
let val = AsciiMetadataValue::from(n);
|
||||
assert_eq!(val, &n.to_string());
|
||||
|
||||
let n = ::std::$t::MAX;
|
||||
let val = AsciiMetadataValue::from(n);
|
||||
assert_eq!(val, &n.to_string());
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
from_integers! {
|
||||
// integer type => maximum decimal length
|
||||
|
||||
// u8 purposely left off... AsciiMetadataValue::from(b'3') could be confusing
|
||||
from_u16: u16 => 5,
|
||||
from_i16: i16 => 6,
|
||||
from_u32: u32 => 10,
|
||||
from_i32: i32 => 11,
|
||||
from_u64: u64 => 20,
|
||||
from_i64: i64 => 20
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "16")]
|
||||
from_integers! {
|
||||
from_usize: usize => 5,
|
||||
from_isize: isize => 6
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
from_integers! {
|
||||
from_usize: usize => 10,
|
||||
from_isize: isize => 11
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
from_integers! {
|
||||
from_usize: usize => 20,
|
||||
from_isize: isize => 20
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod from_metadata_value_tests {
|
||||
use super::*;
|
||||
use crate::metadata::map::MetadataMap;
|
||||
|
||||
#[test]
|
||||
fn it_can_insert_metadata_key_as_metadata_value() {
|
||||
let mut map = MetadataMap::new();
|
||||
map.insert(
|
||||
"accept",
|
||||
MetadataKey::<Ascii>::from_bytes(b"hello-world")
|
||||
.unwrap()
|
||||
.into(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
map.get("accept").unwrap(),
|
||||
AsciiMetadataValue::try_from_bytes(b"hello-world").unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MetadataValue<Ascii> {
|
||||
type Err = InvalidMetadataValue;
|
||||
|
||||
#[inline]
|
||||
fn from_str(s: &str) -> Result<MetadataValue<Ascii>, Self::Err> {
|
||||
MetadataValue::<Ascii>::from_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> From<MetadataValue<VE>> for Bytes {
|
||||
#[inline]
|
||||
fn from(value: MetadataValue<VE>) -> Bytes {
|
||||
Bytes::from(value.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, VE: ValueEncoding> From<&'a MetadataValue<VE>> for MetadataValue<VE> {
|
||||
#[inline]
|
||||
fn from(t: &'a MetadataValue<VE>) -> Self {
|
||||
t.clone()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== ToStrError =====
|
||||
|
||||
impl ToStrError {
|
||||
pub(crate) fn new() -> Self {
|
||||
ToStrError { _priv: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ToStrError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.description().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ToStrError {
|
||||
fn description(&self) -> &str {
|
||||
"failed to convert metadata to a str"
|
||||
}
|
||||
}
|
||||
|
||||
// ===== PartialEq / PartialOrd =====
|
||||
|
||||
impl<VE: ValueEncoding> PartialEq for MetadataValue<VE> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &MetadataValue<VE>) -> bool {
|
||||
// Note: Different binary strings that after base64 decoding
|
||||
// will count as the same value for Binary values. Also,
|
||||
// different invalid base64 values count as equal for Binary
|
||||
// values.
|
||||
VE::values_equal(&self.inner, &other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> Eq for MetadataValue<VE> {}
|
||||
|
||||
impl<VE: ValueEncoding> PartialOrd for MetadataValue<VE> {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &MetadataValue<VE>) -> Option<cmp::Ordering> {
|
||||
self.inner.partial_cmp(&other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> Ord for MetadataValue<VE> {
|
||||
#[inline]
|
||||
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||
self.inner.cmp(&other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> PartialEq<str> for MetadataValue<VE> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
VE::equals(&self.inner, other.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> PartialEq<[u8]> for MetadataValue<VE> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &[u8]) -> bool {
|
||||
VE::equals(&self.inner, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> PartialOrd<str> for MetadataValue<VE> {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
|
||||
self.inner.partial_cmp(other.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> PartialOrd<[u8]> for MetadataValue<VE> {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &[u8]) -> Option<cmp::Ordering> {
|
||||
self.inner.partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> PartialEq<MetadataValue<VE>> for str {
|
||||
#[inline]
|
||||
fn eq(&self, other: &MetadataValue<VE>) -> bool {
|
||||
*other == *self
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> PartialEq<MetadataValue<VE>> for [u8] {
|
||||
#[inline]
|
||||
fn eq(&self, other: &MetadataValue<VE>) -> bool {
|
||||
*other == *self
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> PartialOrd<MetadataValue<VE>> for str {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &MetadataValue<VE>) -> Option<cmp::Ordering> {
|
||||
self.as_bytes().partial_cmp(other.inner.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> PartialOrd<MetadataValue<VE>> for [u8] {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &MetadataValue<VE>) -> Option<cmp::Ordering> {
|
||||
self.partial_cmp(other.inner.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> PartialEq<String> for MetadataValue<VE> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &String) -> bool {
|
||||
*self == &other[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> PartialOrd<String> for MetadataValue<VE> {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
|
||||
self.inner.partial_cmp(other.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> PartialEq<MetadataValue<VE>> for String {
|
||||
#[inline]
|
||||
fn eq(&self, other: &MetadataValue<VE>) -> bool {
|
||||
*other == *self
|
||||
}
|
||||
}
|
||||
|
||||
impl<VE: ValueEncoding> PartialOrd<MetadataValue<VE>> for String {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &MetadataValue<VE>) -> Option<cmp::Ordering> {
|
||||
self.as_bytes().partial_cmp(other.inner.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, VE: ValueEncoding> PartialEq<MetadataValue<VE>> for &'a MetadataValue<VE> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &MetadataValue<VE>) -> bool {
|
||||
**self == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, VE: ValueEncoding> PartialOrd<MetadataValue<VE>> for &'a MetadataValue<VE> {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &MetadataValue<VE>) -> Option<cmp::Ordering> {
|
||||
(**self).partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, VE: ValueEncoding, T: ?Sized> PartialEq<&'a T> for MetadataValue<VE>
|
||||
where
|
||||
MetadataValue<VE>: PartialEq<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &&'a T) -> bool {
|
||||
*self == **other
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, VE: ValueEncoding, T: ?Sized> PartialOrd<&'a T> for MetadataValue<VE>
|
||||
where
|
||||
MetadataValue<VE>: PartialOrd<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &&'a T) -> Option<cmp::Ordering> {
|
||||
self.partial_cmp(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, VE: ValueEncoding> PartialEq<MetadataValue<VE>> for &'a str {
|
||||
#[inline]
|
||||
fn eq(&self, other: &MetadataValue<VE>) -> bool {
|
||||
*other == *self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, VE: ValueEncoding> PartialOrd<MetadataValue<VE>> for &'a str {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &MetadataValue<VE>) -> Option<cmp::Ordering> {
|
||||
self.as_bytes().partial_cmp(other.inner.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let cases = &[
|
||||
("hello", "\"hello\""),
|
||||
("hello \"world\"", "\"hello \\\"world\\\"\""),
|
||||
("\u{7FFF}hello", "\"\\xe7\\xbf\\xbfhello\""),
|
||||
];
|
||||
|
||||
for &(value, expected) in cases {
|
||||
let val = AsciiMetadataValue::try_from_bytes(value.as_bytes()).unwrap();
|
||||
let actual = format!("{:?}", val);
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
let mut sensitive = AsciiMetadataValue::from_static("password");
|
||||
sensitive.set_sensitive(true);
|
||||
assert_eq!("Sensitive", format!("{:?}", sensitive));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_empty() {
|
||||
fn from_str<VE: ValueEncoding>(s: &str) -> MetadataValue<VE> {
|
||||
MetadataValue::<VE>::unchecked_from_header_value(s.parse().unwrap())
|
||||
}
|
||||
|
||||
assert!(from_str::<Ascii>("").is_empty());
|
||||
assert!(from_str::<Binary>("").is_empty());
|
||||
assert!(!from_str::<Ascii>("a").is_empty());
|
||||
assert!(!from_str::<Binary>("a").is_empty());
|
||||
assert!(!from_str::<Ascii>("=").is_empty());
|
||||
assert!(from_str::<Binary>("=").is_empty());
|
||||
assert!(!from_str::<Ascii>("===").is_empty());
|
||||
assert!(from_str::<Binary>("===").is_empty());
|
||||
assert!(!from_str::<Ascii>("=====").is_empty());
|
||||
assert!(from_str::<Binary>("=====").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_shared_base64_encodes() {
|
||||
let value = BinaryMetadataValue::from_shared(Bytes::from_static(b"Hello")).unwrap();
|
||||
assert_eq!(value.as_encoded_bytes(), b"SGVsbG8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_eq_value() {
|
||||
type BMV = BinaryMetadataValue;
|
||||
type AMV = AsciiMetadataValue;
|
||||
|
||||
assert_eq!(AMV::from_static("abc"), AMV::from_static("abc"));
|
||||
assert!(AMV::from_static("abc") != AMV::from_static("ABC"));
|
||||
|
||||
assert_eq!(BMV::from_bytes(b"abc"), BMV::from_bytes(b"abc"));
|
||||
assert!(BMV::from_bytes(b"abc") != BMV::from_bytes(b"ABC"));
|
||||
|
||||
// Padding is ignored.
|
||||
assert_eq!(
|
||||
BMV::from_static("SGVsbG8hIQ=="),
|
||||
BMV::from_static("SGVsbG8hIQ")
|
||||
);
|
||||
// Invalid values are all just invalid from this point of view.
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
BMV::from_shared_unchecked(Bytes::from_static(b"..{}")),
|
||||
BMV::from_shared_unchecked(Bytes::from_static(b"{}.."))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_eq_str() {
|
||||
type BMV = BinaryMetadataValue;
|
||||
type AMV = AsciiMetadataValue;
|
||||
|
||||
assert_eq!(AMV::from_static("abc"), "abc");
|
||||
assert!(AMV::from_static("abc") != "ABC");
|
||||
assert_eq!("abc", AMV::from_static("abc"));
|
||||
assert!("ABC" != AMV::from_static("abc"));
|
||||
|
||||
assert_eq!(BMV::from_bytes(b"abc"), "abc");
|
||||
assert!(BMV::from_bytes(b"abc") != "ABC");
|
||||
assert_eq!("abc", BMV::from_bytes(b"abc"));
|
||||
assert!("ABC" != BMV::from_bytes(b"abc"));
|
||||
|
||||
// Padding is ignored.
|
||||
assert_eq!(BMV::from_static("SGVsbG8hIQ=="), "Hello!!");
|
||||
assert_eq!("Hello!!", BMV::from_static("SGVsbG8hIQ=="));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_eq_bytes() {
|
||||
type BMV = BinaryMetadataValue;
|
||||
type AMV = AsciiMetadataValue;
|
||||
|
||||
assert_eq!(AMV::from_static("abc"), "abc".as_bytes());
|
||||
assert!(AMV::from_static("abc") != "ABC".as_bytes());
|
||||
assert_eq!(*"abc".as_bytes(), AMV::from_static("abc"));
|
||||
assert!(*"ABC".as_bytes() != AMV::from_static("abc"));
|
||||
|
||||
assert_eq!(*"abc".as_bytes(), BMV::from_bytes(b"abc"));
|
||||
assert!(*"ABC".as_bytes() != BMV::from_bytes(b"abc"));
|
||||
|
||||
// Padding is ignored.
|
||||
assert_eq!(BMV::from_static("SGVsbG8hIQ=="), "Hello!!".as_bytes());
|
||||
assert_eq!(*"Hello!!".as_bytes(), BMV::from_static("SGVsbG8hIQ=="));
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
use crate::metadata::MetadataMap;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Request<T> {
|
||||
metadata: MetadataMap,
|
||||
message: T,
|
||||
}
|
||||
|
||||
impl<T> Request<T> {
|
||||
/// Create a new gRPC request
|
||||
pub fn new(message: T) -> Self {
|
||||
Request {
|
||||
metadata: MetadataMap::new(),
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the message
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.message
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the message
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.message
|
||||
}
|
||||
|
||||
/// Get a reference to the custom request metadata.
|
||||
pub fn metadata(&self) -> &MetadataMap {
|
||||
&self.metadata
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the request metadata.
|
||||
pub fn metadata_mut(&mut self) -> &mut MetadataMap {
|
||||
&mut self.metadata
|
||||
}
|
||||
|
||||
/// Consumes `self`, returning the message
|
||||
pub fn into_inner(self) -> T {
|
||||
self.message
|
||||
}
|
||||
|
||||
/// Convert an HTTP request to a gRPC request
|
||||
pub fn from_http(http: http::Request<T>) -> Self {
|
||||
let (head, message) = http.into_parts();
|
||||
Request {
|
||||
metadata: MetadataMap::from_headers(head.headers),
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_http(self, uri: http::Uri) -> http::Request<T> {
|
||||
let mut request = http::Request::new(self.message);
|
||||
|
||||
*request.version_mut() = http::Version::HTTP_2;
|
||||
*request.method_mut() = http::Method::POST;
|
||||
*request.uri_mut() = uri;
|
||||
*request.headers_mut() = self.metadata.into_headers();
|
||||
|
||||
request
|
||||
}
|
||||
|
||||
pub fn map<F, U>(self, f: F) -> Request<U>
|
||||
where
|
||||
F: FnOnce(T) -> U,
|
||||
{
|
||||
let message = f(self.message);
|
||||
|
||||
Request {
|
||||
metadata: self.metadata,
|
||||
message,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use crate::metadata::MetadataMap;
|
||||
|
||||
/// A gRPC response and metadata from an RPC call.
|
||||
#[derive(Debug)]
|
||||
pub struct Response<T> {
|
||||
metadata: MetadataMap,
|
||||
message: T,
|
||||
}
|
||||
|
||||
impl<T> Response<T> {
|
||||
/// Create a new gRPC response.
|
||||
pub fn new(message: T) -> Self {
|
||||
Response {
|
||||
metadata: MetadataMap::new(),
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the message
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.message
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the message
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.message
|
||||
}
|
||||
|
||||
/// Get a reference to the custom response metadata.
|
||||
pub fn metadata(&self) -> &MetadataMap {
|
||||
&self.metadata
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the response metadata.
|
||||
pub fn metadata_mut(&mut self) -> &mut MetadataMap {
|
||||
&mut self.metadata
|
||||
}
|
||||
|
||||
/// Consumes `self`, returning the message
|
||||
pub fn into_inner(self) -> T {
|
||||
self.message
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn from_http(res: http::Response<T>) -> Self {
|
||||
let (head, message) = res.into_parts();
|
||||
Response {
|
||||
metadata: MetadataMap::from_headers(head.headers),
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_http(self) -> http::Response<T> {
|
||||
let mut res = http::Response::new(self.message);
|
||||
|
||||
*res.version_mut() = http::Version::HTTP_2;
|
||||
*res.headers_mut() = self.metadata.into_headers();
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn map<F, U>(self, f: F) -> Response<U>
|
||||
where
|
||||
F: FnOnce(T) -> U,
|
||||
{
|
||||
let message = f(self.message);
|
||||
Response {
|
||||
metadata: self.metadata,
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn metadata()
|
||||
// pub fn metadata_bin()
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
use crate::{Request, Response, Status};
|
||||
use async_stream::stream;
|
||||
use bytes::{Bytes, BytesMut, IntoBuf};
|
||||
use futures_core::TryStream;
|
||||
use futures_util::{stream, StreamExt, TryStreamExt};
|
||||
use std::future::Future;
|
||||
use tokio_codec::{Decoder, Encoder};
|
||||
use tower_service::Service;
|
||||
|
||||
#[allow(dead_code)]
|
||||
type Result<T> = std::result::Result<Response<T>, Status>;
|
||||
|
||||
pub trait Codec {
|
||||
type Encode;
|
||||
type Decode;
|
||||
}
|
||||
|
||||
pub struct Encode<T, U> {
|
||||
inner: T,
|
||||
source: U,
|
||||
}
|
||||
|
||||
impl<T, U> Encode<T, U>
|
||||
where
|
||||
T: Encoder,
|
||||
U: TryStream<Ok = T::Item, Error = Status> + Unpin,
|
||||
{
|
||||
pub fn new(inner: T, source: U) -> Self {
|
||||
Encode { inner, source }
|
||||
}
|
||||
|
||||
pub fn encode<'a>(
|
||||
&'a mut self,
|
||||
buf: &'a mut BytesMut,
|
||||
) -> impl TryStream<Ok = crate::body::BytesBuf, Error = Status> + 'a {
|
||||
stream! {
|
||||
loop {
|
||||
match self.source.try_next().await {
|
||||
Ok(Some(item)) => {
|
||||
self.inner.encode(item, buf).map_err(drop).unwrap();
|
||||
let len = buf.len();
|
||||
yield Ok(buf.split_to(len).freeze().into_buf());
|
||||
},
|
||||
Ok(None) => break,
|
||||
Err(status) => yield Err(status),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::body::AsyncBody;
|
||||
use crate::server::Encode;
|
||||
use bytes::Bytes;
|
||||
use tokio_codec::BytesCodec;
|
||||
|
||||
#[test]
|
||||
fn body() {
|
||||
let stream = futures_util::stream::iter(vec![Ok(Bytes::new())]);
|
||||
let encode = Encode::new(BytesCodec::new(), stream);
|
||||
}
|
||||
}
|
||||
|
||||
// impl<T, U> http_body::Body for Encode<T, U>
|
||||
|
||||
// pub struct Grpc<T> {
|
||||
// opdec: T,
|
||||
// }
|
||||
|
||||
// impl<T: Codec> Grpc<T> {
|
||||
// pub async fn unary<B>(&mut self, message: B) -> Result<Response<B>> {
|
||||
// self.server_streaming(stream::once(message)).await
|
||||
// }
|
||||
|
||||
// pub async fn server_streaming<B>(
|
||||
// &mut self,
|
||||
// stream: impl Stream,
|
||||
// ) -> Result<Response<impl http_body::Body>> {
|
||||
// unimplemetned!()
|
||||
// }
|
||||
|
||||
// fn map_request<B>(&mut self, request: http::Request<B>) -> Request<B> {
|
||||
// Request::from_http(request)
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,520 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use bytes::Bytes;
|
||||
use http::header::HeaderValue;
|
||||
use http::{self, HeaderMap};
|
||||
use percent_encoding::{percent_decode, percent_encode, EncodeSet, DEFAULT_ENCODE_SET};
|
||||
use std::{error::Error, fmt};
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
const GRPC_STATUS_HEADER_CODE: &str = "grpc-status";
|
||||
const GRPC_STATUS_MESSAGE_HEADER: &str = "grpc-message";
|
||||
const GRPC_STATUS_DETAILS_HEADER: &str = "grpc-status-details-bin";
|
||||
|
||||
/// A gRPC "status" describing the result of an RPC call.
|
||||
#[derive(Clone)]
|
||||
pub struct Status {
|
||||
/// The gRPC status code, found in the `grpc-status` header.
|
||||
code: Code,
|
||||
/// A relevant error message, found in the `grpc-message` header.
|
||||
message: String,
|
||||
/// Binary opaque details, found in the `grpc-status-details-bin` header.
|
||||
details: Bytes,
|
||||
}
|
||||
|
||||
/// gRPC status codes used by `Status`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Code {
|
||||
Ok = 0,
|
||||
Cancelled = 1,
|
||||
Unknown = 2,
|
||||
InvalidArgument = 3,
|
||||
DeadlineExceeded = 4,
|
||||
NotFound = 5,
|
||||
AlreadyExists = 6,
|
||||
PermissionDenied = 7,
|
||||
ResourceExhausted = 8,
|
||||
FailedPrecondition = 9,
|
||||
Aborted = 10,
|
||||
OutOfRange = 11,
|
||||
Unimplemented = 12,
|
||||
Internal = 13,
|
||||
Unavailable = 14,
|
||||
DataLoss = 15,
|
||||
Unauthenticated = 16,
|
||||
|
||||
// New Codes may be added in the future, so never exhaustively match!
|
||||
#[doc(hidden)]
|
||||
__NonExhaustive,
|
||||
}
|
||||
|
||||
// ===== impl Status =====
|
||||
|
||||
impl Status {
|
||||
/// Create a new `Status` with the associated code and message.
|
||||
pub fn new(code: Code, message: impl Into<String>) -> Status {
|
||||
Status {
|
||||
code,
|
||||
message: message.into(),
|
||||
details: Bytes::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: this constructor encourages creating statuses with no
|
||||
// message, hurting later debugging.
|
||||
#[doc(hidden)]
|
||||
#[deprecated(note = "use State::new")]
|
||||
pub fn with_code(code: Code) -> Status {
|
||||
Status::new(code, String::new())
|
||||
}
|
||||
|
||||
// Deprecated: this constructor is overly long.
|
||||
#[doc(hidden)]
|
||||
#[deprecated(note = "use State::new")]
|
||||
pub fn with_code_and_message(code: Code, message: String) -> Status {
|
||||
Status::new(code, message)
|
||||
}
|
||||
|
||||
// TODO: This should probably be made public eventually. Need to decide on
|
||||
// the exact argument type.
|
||||
#[cfg_attr(not(feature = "h2"), allow(dead_code))]
|
||||
pub(crate) fn from_error(err: &(dyn Error + 'static)) -> Status {
|
||||
Status::try_from_error(err).unwrap_or_else(|| Status::new(Code::Unknown, err.to_string()))
|
||||
}
|
||||
|
||||
fn try_from_error(err: &(dyn Error + 'static)) -> Option<Status> {
|
||||
let mut cause = Some(err);
|
||||
|
||||
while let Some(err) = cause {
|
||||
if let Some(status) = err.downcast_ref::<Status>() {
|
||||
return Some(Status {
|
||||
code: status.code,
|
||||
message: status.message.clone(),
|
||||
details: status.details.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "h2")]
|
||||
{
|
||||
if let Some(h2) = err.downcast_ref::<h2::Error>() {
|
||||
return Some(Status::from_h2_error(h2));
|
||||
}
|
||||
}
|
||||
|
||||
cause = err.source();
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "h2")]
|
||||
fn from_h2_error(err: &h2::Error) -> Status {
|
||||
// See https://github.com/grpc/grpc/blob/3977c30/doc/PROTOCOL-HTTP2.md#errors
|
||||
let code = match err.reason() {
|
||||
Some(h2::Reason::NO_ERROR)
|
||||
| Some(h2::Reason::PROTOCOL_ERROR)
|
||||
| Some(h2::Reason::INTERNAL_ERROR)
|
||||
| Some(h2::Reason::FLOW_CONTROL_ERROR)
|
||||
| Some(h2::Reason::SETTINGS_TIMEOUT)
|
||||
| Some(h2::Reason::COMPRESSION_ERROR)
|
||||
| Some(h2::Reason::CONNECT_ERROR) => Code::Internal,
|
||||
Some(h2::Reason::REFUSED_STREAM) => Code::Unavailable,
|
||||
Some(h2::Reason::CANCEL) => Code::Cancelled,
|
||||
Some(h2::Reason::ENHANCE_YOUR_CALM) => Code::ResourceExhausted,
|
||||
Some(h2::Reason::INADEQUATE_SECURITY) => Code::PermissionDenied,
|
||||
|
||||
_ => Code::Unknown,
|
||||
};
|
||||
|
||||
Status::new(code, format!("h2 protocol error: {}", err))
|
||||
}
|
||||
|
||||
#[cfg(feature = "h2")]
|
||||
fn to_h2_error(&self) -> h2::Error {
|
||||
// conservatively transform to h2 error codes...
|
||||
let reason = match self.code {
|
||||
Code::Cancelled => h2::Reason::CANCEL,
|
||||
_ => h2::Reason::INTERNAL_ERROR,
|
||||
};
|
||||
|
||||
reason.into()
|
||||
}
|
||||
|
||||
pub(crate) fn map_error<E>(err: E) -> Status
|
||||
where
|
||||
E: Into<Box<dyn Error + Send + Sync>>,
|
||||
{
|
||||
Status::from_error(&*err.into())
|
||||
}
|
||||
|
||||
pub(crate) fn from_header_map(header_map: &HeaderMap) -> Option<Status> {
|
||||
header_map.get(GRPC_STATUS_HEADER_CODE).map(|code| {
|
||||
let code = Code::from_bytes(code.as_ref());
|
||||
let error_message = header_map
|
||||
.get(GRPC_STATUS_MESSAGE_HEADER)
|
||||
.map(|header| {
|
||||
percent_decode(header.as_bytes())
|
||||
.decode_utf8()
|
||||
.map(|cow| cow.to_string())
|
||||
})
|
||||
.unwrap_or_else(|| Ok(String::new()));
|
||||
let details = header_map
|
||||
.get(GRPC_STATUS_DETAILS_HEADER)
|
||||
.map(|h| Bytes::from(h.as_bytes()))
|
||||
.unwrap_or_else(Bytes::new);
|
||||
match error_message {
|
||||
Ok(message) => Status {
|
||||
code,
|
||||
message,
|
||||
details,
|
||||
},
|
||||
Err(err) => {
|
||||
warn!("Error deserializing status message header: {}", err);
|
||||
Status {
|
||||
code: Code::Unknown,
|
||||
message: format!("Error deserializing status message header: {}", err),
|
||||
details,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the gRPC `Code` of this `Status`.
|
||||
pub fn code(&self) -> Code {
|
||||
self.code
|
||||
}
|
||||
|
||||
/// Get the text error message of this `Status`.
|
||||
pub fn message(&self) -> &str {
|
||||
&self.message
|
||||
}
|
||||
|
||||
/// Get the opaque error details of this `Status`.
|
||||
pub fn details(&self) -> &[u8] {
|
||||
&self.details
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(note = "use Status::message")]
|
||||
pub fn error_message(&self) -> &str {
|
||||
&self.message
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(note = "use Status::details")]
|
||||
pub fn binary_error_details(&self) -> &Bytes {
|
||||
&self.details
|
||||
}
|
||||
|
||||
pub(crate) fn to_header_map(&self) -> Result<HeaderMap, Self> {
|
||||
let mut header_map = HeaderMap::with_capacity(3);
|
||||
self.add_header(&mut header_map)?;
|
||||
Ok(header_map)
|
||||
}
|
||||
|
||||
pub(crate) fn add_header(&self, header_map: &mut HeaderMap) -> Result<(), Self> {
|
||||
header_map.insert(GRPC_STATUS_HEADER_CODE, self.code.to_header_value());
|
||||
|
||||
if !self.message.is_empty() {
|
||||
let is_need_encode = self
|
||||
.message
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.any(|&x| DEFAULT_ENCODE_SET.contains(x));
|
||||
let to_write = if is_need_encode {
|
||||
percent_encode(&self.message().as_bytes(), DEFAULT_ENCODE_SET)
|
||||
.to_string()
|
||||
.into()
|
||||
} else {
|
||||
Bytes::from(self.message().as_bytes())
|
||||
};
|
||||
|
||||
header_map.insert(
|
||||
GRPC_STATUS_MESSAGE_HEADER,
|
||||
HeaderValue::from_shared(to_write).map_err(invalid_header_value_byte)?,
|
||||
);
|
||||
}
|
||||
|
||||
if !self.details.is_empty() {
|
||||
header_map.insert(
|
||||
GRPC_STATUS_DETAILS_HEADER,
|
||||
HeaderValue::from_shared(self.details.clone())
|
||||
.map_err(invalid_header_value_byte)?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Status {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// A manual impl to reduce the noise of frequently empty fields.
|
||||
let mut builder = f.debug_struct("Status");
|
||||
|
||||
builder.field("code", &self.code);
|
||||
|
||||
if !self.message.is_empty() {
|
||||
builder.field("message", &self.message);
|
||||
}
|
||||
|
||||
if !self.details.is_empty() {
|
||||
builder.field("details", &self.details);
|
||||
}
|
||||
|
||||
builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid_header_value_byte<Error: fmt::Display>(err: Error) -> Status {
|
||||
debug!("Invalid header: {}", err);
|
||||
Status::new(
|
||||
Code::Internal,
|
||||
"Couldn't serialize non-text grpc status header".to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "h2")]
|
||||
impl From<h2::Error> for Status {
|
||||
fn from(err: h2::Error) -> Self {
|
||||
Status::from_h2_error(&err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "h2")]
|
||||
impl From<Status> for h2::Error {
|
||||
fn from(status: Status) -> Self {
|
||||
status.to_h2_error()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Status {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"grpc-status: {:?}, grpc-message: {:?}",
|
||||
self.code(),
|
||||
self.message()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for Status {}
|
||||
|
||||
///
|
||||
/// Take the `Status` value from `trailers` if it is available, else from `status_code`.
|
||||
///
|
||||
pub(crate) fn infer_grpc_status(
|
||||
trailers: Option<HeaderMap>,
|
||||
status_code: http::StatusCode,
|
||||
) -> Result<(), Status> {
|
||||
if let Some(trailers) = trailers {
|
||||
if let Some(status) = Status::from_header_map(&trailers) {
|
||||
if status.code() == Code::Ok {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
trace!("trailers missing grpc-status");
|
||||
let code = match status_code {
|
||||
// Borrowed from https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
|
||||
http::StatusCode::BAD_REQUEST => Code::Internal,
|
||||
http::StatusCode::UNAUTHORIZED => Code::Unauthenticated,
|
||||
http::StatusCode::FORBIDDEN => Code::PermissionDenied,
|
||||
http::StatusCode::NOT_FOUND => Code::Unimplemented,
|
||||
http::StatusCode::TOO_MANY_REQUESTS
|
||||
| http::StatusCode::BAD_GATEWAY
|
||||
| http::StatusCode::SERVICE_UNAVAILABLE
|
||||
| http::StatusCode::GATEWAY_TIMEOUT => Code::Unavailable,
|
||||
_ => Code::Unknown,
|
||||
};
|
||||
|
||||
let msg = format!(
|
||||
"grpc-status header missing, mapped from HTTP status code {}",
|
||||
status_code.as_u16(),
|
||||
);
|
||||
let status = Status::new(code, msg);
|
||||
Err(status)
|
||||
}
|
||||
|
||||
// ===== impl Code =====
|
||||
|
||||
impl Code {
|
||||
/// Get the `Code` that represents the integer, if known.
|
||||
///
|
||||
/// If not known, returns `Code::Unknown` (surprise!).
|
||||
pub fn from_i32(i: i32) -> Code {
|
||||
Code::from(i)
|
||||
}
|
||||
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Code {
|
||||
match bytes.len() {
|
||||
1 => match bytes[0] {
|
||||
b'0' => Code::Ok,
|
||||
b'1' => Code::Cancelled,
|
||||
b'2' => Code::Unknown,
|
||||
b'3' => Code::InvalidArgument,
|
||||
b'4' => Code::DeadlineExceeded,
|
||||
b'5' => Code::NotFound,
|
||||
b'6' => Code::AlreadyExists,
|
||||
b'7' => Code::PermissionDenied,
|
||||
b'8' => Code::ResourceExhausted,
|
||||
b'9' => Code::FailedPrecondition,
|
||||
_ => Code::parse_err(),
|
||||
},
|
||||
2 => match (bytes[0], bytes[1]) {
|
||||
(b'1', b'0') => Code::Aborted,
|
||||
(b'1', b'1') => Code::OutOfRange,
|
||||
(b'1', b'2') => Code::Unimplemented,
|
||||
(b'1', b'3') => Code::Internal,
|
||||
(b'1', b'4') => Code::Unavailable,
|
||||
(b'1', b'5') => Code::DataLoss,
|
||||
(b'1', b'6') => Code::Unauthenticated,
|
||||
_ => Code::parse_err(),
|
||||
},
|
||||
_ => Code::parse_err(),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_header_value(&self) -> HeaderValue {
|
||||
match self {
|
||||
Code::Ok => HeaderValue::from_static("0"),
|
||||
Code::Cancelled => HeaderValue::from_static("1"),
|
||||
Code::Unknown => HeaderValue::from_static("2"),
|
||||
Code::InvalidArgument => HeaderValue::from_static("3"),
|
||||
Code::DeadlineExceeded => HeaderValue::from_static("4"),
|
||||
Code::NotFound => HeaderValue::from_static("5"),
|
||||
Code::AlreadyExists => HeaderValue::from_static("6"),
|
||||
Code::PermissionDenied => HeaderValue::from_static("7"),
|
||||
Code::ResourceExhausted => HeaderValue::from_static("8"),
|
||||
Code::FailedPrecondition => HeaderValue::from_static("9"),
|
||||
Code::Aborted => HeaderValue::from_static("10"),
|
||||
Code::OutOfRange => HeaderValue::from_static("11"),
|
||||
Code::Unimplemented => HeaderValue::from_static("12"),
|
||||
Code::Internal => HeaderValue::from_static("13"),
|
||||
Code::Unavailable => HeaderValue::from_static("14"),
|
||||
Code::DataLoss => HeaderValue::from_static("15"),
|
||||
Code::Unauthenticated => HeaderValue::from_static("16"),
|
||||
|
||||
Code::__NonExhaustive => unreachable!("Code::__NonExhaustive"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn parse_err() -> Code {
|
||||
trace!("error parsing grpc-status");
|
||||
Code::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Code {
|
||||
fn from(i: i32) -> Self {
|
||||
match i {
|
||||
0 => Code::Ok,
|
||||
1 => Code::Cancelled,
|
||||
2 => Code::Unknown,
|
||||
3 => Code::InvalidArgument,
|
||||
4 => Code::DeadlineExceeded,
|
||||
5 => Code::NotFound,
|
||||
6 => Code::AlreadyExists,
|
||||
7 => Code::PermissionDenied,
|
||||
8 => Code::ResourceExhausted,
|
||||
9 => Code::FailedPrecondition,
|
||||
10 => Code::Aborted,
|
||||
11 => Code::OutOfRange,
|
||||
12 => Code::Unimplemented,
|
||||
13 => Code::Internal,
|
||||
14 => Code::Unavailable,
|
||||
15 => Code::DataLoss,
|
||||
16 => Code::Unauthenticated,
|
||||
|
||||
_ => Code::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Nested(Error);
|
||||
|
||||
impl fmt::Display for Nested {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "nested error: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Nested {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
Some(&*self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_error_status() {
|
||||
let orig = Status::new(Code::OutOfRange, "weeaboo");
|
||||
let found = Status::from_error(&orig);
|
||||
|
||||
assert_eq!(orig.code(), found.code());
|
||||
assert_eq!(orig.message(), found.message());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_error_unknown() {
|
||||
let orig: Error = "peek-a-boo".into();
|
||||
let found = Status::from_error(&*orig);
|
||||
|
||||
assert_eq!(found.code(), Code::Unknown);
|
||||
assert_eq!(found.message(), orig.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_error_nested() {
|
||||
let orig = Nested(Box::new(Status::new(Code::OutOfRange, "weeaboo")));
|
||||
let found = Status::from_error(&orig);
|
||||
|
||||
assert_eq!(found.code(), Code::OutOfRange);
|
||||
assert_eq!(found.message(), "weeaboo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "h2")]
|
||||
fn from_error_h2() {
|
||||
let orig = h2::Error::from(h2::Reason::CANCEL);
|
||||
let found = Status::from_error(&orig);
|
||||
|
||||
assert_eq!(found.code(), Code::Cancelled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "h2")]
|
||||
fn to_h2_error() {
|
||||
let orig = Status::new(Code::Cancelled, "stop eet!");
|
||||
let err = orig.to_h2_error();
|
||||
|
||||
assert_eq!(err.reason(), Some(h2::Reason::CANCEL));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_from_i32() {
|
||||
// This for loop should catch if we ever add a new variant and don't
|
||||
// update From<i32>.
|
||||
for i in 0..(Code::__NonExhaustive as i32) {
|
||||
let code = Code::from(i);
|
||||
assert_eq!(
|
||||
i, code as i32,
|
||||
"Code::from({}) returned {:?} which is {}",
|
||||
i, code, code as i32,
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(Code::from(-1), Code::Unknown);
|
||||
assert_eq!(Code::from(Code::__NonExhaustive as i32), Code::Unknown);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@
|
||||
use futures_util::future;
|
||||
use http::{Request, Response};
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_buf::BufStream;
|
||||
use tower_h2::{RecvBody, Server};
|
||||
use tower_service::Service;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
const ROOT: &'static str = "/";
|
||||
|
||||
|
||||
+22
-22
@@ -30,7 +30,7 @@ where
|
||||
Self {
|
||||
maker,
|
||||
builder,
|
||||
_pd: PhantomData
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,30 +79,30 @@ where
|
||||
}
|
||||
|
||||
pub async fn handle_request<B>(
|
||||
response: Response<B>,
|
||||
mut send_response: h2::server::SendResponse<SendBuf<B::Data>>,
|
||||
) where
|
||||
B: Body + Send + Unpin + 'static,
|
||||
B::Data: Unpin,
|
||||
B::Error: Into<Box<dyn std::error::Error>>,
|
||||
{
|
||||
let (parts, body) = response.into_parts();
|
||||
response: Response<B>,
|
||||
mut send_response: h2::server::SendResponse<SendBuf<B::Data>>,
|
||||
) where
|
||||
B: Body + Send + Unpin + 'static,
|
||||
B::Data: Unpin,
|
||||
B::Error: Into<Box<dyn std::error::Error>>,
|
||||
{
|
||||
let (parts, body) = response.into_parts();
|
||||
|
||||
// Check if the response is imemdiately an end-of-stream.
|
||||
let eos = body.is_end_stream();
|
||||
// Check if the response is imemdiately an end-of-stream.
|
||||
let eos = body.is_end_stream();
|
||||
|
||||
let response = Response::from_parts(parts, ());
|
||||
let response = Response::from_parts(parts, ());
|
||||
|
||||
match send_response.send_response(response, eos) {
|
||||
Ok(sr) => {
|
||||
if eos {
|
||||
return;
|
||||
}
|
||||
|
||||
Flush::new(body, sr).await.unwrap();
|
||||
}
|
||||
Err(e) => {
|
||||
println!("h2 server ERROR={}", e);
|
||||
match send_response.send_response(response, eos) {
|
||||
Ok(sr) => {
|
||||
if eos {
|
||||
return;
|
||||
}
|
||||
|
||||
Flush::new(body, sr).await.unwrap();
|
||||
}
|
||||
Err(e) => {
|
||||
println!("h2 server ERROR={}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user