feat(transport): port router to axum (#830)

This commit is contained in:
David Pedersen
2022-02-18 18:19:52 +01:00
committed by GitHub
parent 0f7344a75f
commit 6dfc20e1db
8 changed files with 151 additions and 315 deletions
+1 -1
View File
@@ -124,7 +124,7 @@ pub fn generate<T: Service>(
B::Error: Into<StdError> + Send + 'static,
{
type Response = http::Response<tonic::body::BoxBody>;
type Error = Never;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+2
View File
@@ -32,6 +32,7 @@ tls-roots = ["tls-roots-common", "rustls-native-certs"]
tls-roots-common = ["tls"]
tls-webpki-roots = ["tls-roots-common", "webpki-roots"]
transport = [
"axum",
"h2",
"hyper",
"tokio",
@@ -77,6 +78,7 @@ tokio = {version = "1.0.1", features = ["net"], optional = true}
tokio-stream = "0.1"
tower = {version = "0.4.7", features = ["balance", "buffer", "discover", "limit", "load", "make", "timeout", "util"], optional = true}
tracing-futures = {version = "0.2", optional = true}
axum = {version = "0.4", default_features = false, optional = true}
# rustls
rustls-pemfile = { version = "0.2.1", optional = true }
+9
View File
@@ -5,6 +5,15 @@ use http_body::Body;
/// A type erased HTTP body used for tonic services.
pub type BoxBody = http_body::combinators::UnsyncBoxBody<bytes::Bytes, crate::Status>;
/// Convert a [`http_body::Body`] into a [`BoxBody`].
pub(crate) fn boxed<B>(body: B) -> BoxBody
where
B: http_body::Body<Data = bytes::Bytes> + Send + 'static,
B::Error: Into<crate::Error>,
{
body.map_err(crate::Status::map_error).boxed_unsync()
}
// this also exists in `crate::codegen` but we need it here since `codegen` has
// `#[cfg(feature = "codegen")]`.
/// Create an empty `BoxBody`
-11
View File
@@ -24,17 +24,6 @@ pub mod http {
pub use http::*;
}
#[derive(Debug)]
pub enum Never {}
impl std::fmt::Display for Never {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {}
}
}
impl std::error::Error for Never {}
pub fn empty_body() -> crate::body::BoxBody {
http_body::Empty::new()
.map_err(|err| match err {})
+24 -13
View File
@@ -2,7 +2,11 @@
//!
//! See [`Interceptor`] for more details.
use crate::{request::SanitizeHeaders, Status};
use crate::{
body::{boxed, BoxBody},
request::SanitizeHeaders,
Status,
};
use bytes::Bytes;
use pin_project::pin_project;
use std::{
@@ -145,15 +149,16 @@ where
F: Interceptor,
S: Service<http::Request<ReqBody>, Response = http::Response<ResBody>>,
S::Error: Into<crate::Error>,
ResBody: http_body::Body<Data = bytes::Bytes> + Send + 'static,
ResBody::Error: Into<crate::Error>,
{
type Response = http::Response<ResBody>;
type Error = crate::Error;
type Response = http::Response<BoxBody>;
type Error = S::Error;
type Future = ResponseFuture<S::Future>;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx).map_err(Into::into)
self.inner.poll_ready(cx)
}
fn call(&mut self, req: http::Request<ReqBody>) -> Self::Future {
@@ -171,7 +176,7 @@ where
let req = req.into_http(uri, SanitizeHeaders::No);
ResponseFuture::future(self.inner.call(req))
}
Err(status) => ResponseFuture::error(status),
Err(status) => ResponseFuture::status(status),
}
}
}
@@ -200,9 +205,9 @@ impl<F> ResponseFuture<F> {
}
}
fn error(status: Status) -> Self {
fn status(status: Status) -> Self {
Self {
kind: Kind::Error(Some(status)),
kind: Kind::Status(Some(status)),
}
}
}
@@ -211,7 +216,7 @@ impl<F> ResponseFuture<F> {
#[derive(Debug)]
enum Kind<F> {
Future(#[pin] F),
Error(Option<Status>),
Status(Option<Status>),
}
impl<F, E, B> Future for ResponseFuture<F>
@@ -221,14 +226,20 @@ where
B: Default + http_body::Body<Data = Bytes> + Send + 'static,
B::Error: Into<crate::Error>,
{
type Output = Result<http::Response<B>, crate::Error>;
type Output = Result<http::Response<BoxBody>, E>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project().kind.project() {
KindProj::Future(future) => future.poll(cx).map_err(Into::into),
KindProj::Error(status) => {
let response = status.take().unwrap().to_http().map(|_| B::default());
KindProj::Future(future) => future
.poll(cx)
.map(|result| result.map(|res| res.map(boxed))),
KindProj::Status(status) => {
let response = status
.take()
.unwrap()
.to_http()
.map(|_| B::default())
.map(boxed);
Poll::Ready(Ok(response))
}
}
+44 -177
View File
@@ -9,6 +9,7 @@ mod tls;
#[cfg(unix)]
mod unix;
pub use super::service::Routes;
pub use conn::{Connected, TcpConnectInfo};
#[cfg(feature = "tls")]
pub use tls::ServerTlsConfig;
@@ -31,19 +32,17 @@ pub(crate) use tokio_rustls::server::TlsStream;
use crate::transport::Error;
use self::recover_error::RecoverError;
use super::service::{GrpcTimeout, Or, Routes, ServerIo};
use super::service::{GrpcTimeout, ServerIo};
use crate::body::BoxBody;
use bytes::Bytes;
use futures_core::Stream;
use futures_util::{
future::{self, MapErr},
ready, TryFutureExt,
};
use futures_util::{future, ready};
use http::{Request, Response};
use http_body::Body as _;
use hyper::{server::accept, Body};
use pin_project::pin_project;
use std::{
convert::Infallible,
fmt,
future::Future,
marker::PhantomData,
@@ -94,41 +93,9 @@ pub struct Server<L = Identity> {
/// A stack based `Service` router.
#[derive(Debug)]
pub struct Router<A, B, L = Identity> {
pub struct Router<L = Identity> {
server: Server<L>,
routes: Routes<A, B, Request<Body>>,
}
/// A service that is produced from a Tonic `Router`.
///
/// This service implementation will route between multiple Tonic
/// gRPC endpoints and can be consumed with the rest of the `tower`
/// ecosystem.
#[derive(Debug, Clone)]
pub struct RouterService<S> {
inner: S,
}
impl<S> Service<Request<Body>> for RouterService<S>
where
S: Service<Request<Body>, Response = Response<BoxBody>> + Clone + Send + 'static,
S::Future: Send + 'static,
S::Error: Into<crate::Error> + Send,
{
type Response = Response<BoxBody>;
type Error = crate::Error;
#[allow(clippy::type_complexity)]
type Future = MapErr<S::Future, fn(S::Error) -> crate::Error>;
#[inline]
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
self.inner.call(req).map_err(Into::into)
}
routes: Routes,
}
/// A trait to provide a static reference to the service's
@@ -330,18 +297,17 @@ impl<L> Server<L> {
///
/// This will clone the `Server` builder and create a router that will
/// route around different services.
pub fn add_service<S>(&mut self, svc: S) -> Router<S, Unimplemented, L>
pub fn add_service<S>(&mut self, svc: S) -> Router<L>
where
S: Service<Request<Body>, Response = Response<BoxBody>>
S: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible>
+ NamedService
+ Clone
+ Send
+ 'static,
S::Future: Send + 'static,
S::Error: Into<crate::Error> + Send,
L: Clone,
{
Router::new(self.clone(), svc)
Router::new(self.clone(), Routes::new(svc))
}
/// Create a router with the optional `S` typed service as the first service.
@@ -352,25 +318,18 @@ impl<L> Server<L> {
/// # Note
/// Even when the argument given is `None` this will capture *all* requests to this service name.
/// As a result, one cannot use this to toggle between two identically named implementations.
pub fn add_optional_service<S>(
&mut self,
svc: Option<S>,
) -> Router<Either<S, Unimplemented>, Unimplemented, L>
pub fn add_optional_service<S>(&mut self, svc: Option<S>) -> Router<L>
where
S: Service<Request<Body>, Response = Response<BoxBody>>
S: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible>
+ NamedService
+ Clone
+ Send
+ 'static,
S::Future: Send + 'static,
S::Error: Into<crate::Error> + Send,
L: Clone,
{
let svc = match svc {
Some(some) => Either::A(some),
None => Either::B(Unimplemented::default()),
};
Router::new(self.clone(), svc)
let routes = svc.map(Routes::new).unwrap_or_default();
Router::new(self.clone(), routes)
}
/// Set the [Tower] [`Layer`] all services will be wrapped in.
@@ -523,63 +482,25 @@ impl<L> Server<L> {
}
}
impl<S, L> Router<S, Unimplemented, L> {
pub(crate) fn new(server: Server<L>, svc: S) -> Self
where
S: Service<Request<Body>, Response = Response<BoxBody>>
+ NamedService
+ Clone
+ Send
+ 'static,
S::Future: Send + 'static,
S::Error: Into<crate::Error> + Send,
{
let svc_name = <S as NamedService>::NAME;
let svc_route = format!("/{}", svc_name);
let pred = move |req: &Request<Body>| {
let path = req.uri().path();
path.starts_with(&svc_route)
};
Self {
server,
routes: Routes::new(pred, svc, Unimplemented::default()),
}
impl<L> Router<L> {
pub(crate) fn new(server: Server<L>, routes: Routes) -> Self {
Self { server, routes }
}
}
impl<A, B, L> Router<A, B, L>
where
A: Service<Request<Body>, Response = Response<BoxBody>> + Clone + Send + 'static,
A::Future: Send + 'static,
A::Error: Into<crate::Error> + Send,
B: Service<Request<Body>, Response = Response<BoxBody>> + Clone + Send + 'static,
B::Future: Send + 'static,
B::Error: Into<crate::Error> + Send,
{
impl<L> Router<L> {
/// Add a new service to this router.
pub fn add_service<S>(self, svc: S) -> Router<S, Or<A, B, Request<Body>>, L>
pub fn add_service<S>(mut self, svc: S) -> Self
where
S: Service<Request<Body>, Response = Response<BoxBody>>
S: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible>
+ NamedService
+ Clone
+ Send
+ 'static,
S::Future: Send + 'static,
S::Error: Into<crate::Error> + Send,
{
let Self { routes, server } = self;
let svc_name = <S as NamedService>::NAME;
let svc_route = format!("/{}", svc_name);
let pred = move |req: &Request<Body>| {
let path = req.uri().path();
path.starts_with(&svc_route)
};
let routes = routes.push(pred, svc);
Router { server, routes }
self.routes = self.routes.add_service(svc);
self
}
/// Add a new optional service to this router.
@@ -588,35 +509,19 @@ where
/// Even when the argument given is `None` this will capture *all* requests to this service name.
/// As a result, one cannot use this to toggle between two identically named implementations.
#[allow(clippy::type_complexity)]
pub fn add_optional_service<S>(
self,
svc: Option<S>,
) -> Router<Either<S, Unimplemented>, Or<A, B, Request<Body>>, L>
pub fn add_optional_service<S>(mut self, svc: Option<S>) -> Self
where
S: Service<Request<Body>, Response = Response<BoxBody>>
S: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible>
+ NamedService
+ Clone
+ Send
+ 'static,
S::Future: Send + 'static,
S::Error: Into<crate::Error> + Send,
{
let Self { routes, server } = self;
let svc_name = <S as NamedService>::NAME;
let svc_route = format!("/{}", svc_name);
let pred = move |req: &Request<Body>| {
let path = req.uri().path();
path.starts_with(&svc_route)
};
let svc = match svc {
Some(some) => Either::A(some),
None => Either::B(Unimplemented::default()),
};
let routes = routes.push(pred, svc);
Router { server, routes }
if let Some(svc) = svc {
self.routes = self.routes.add_service(svc);
}
self
}
/// Consume this [`Server`] creating a future that will execute the server
@@ -626,12 +531,10 @@ where
/// [tokio]: https://docs.rs/tokio
pub async fn serve<ResBody>(self, addr: SocketAddr) -> Result<(), super::Error>
where
L: Layer<Routes<A, B, Request<Body>>>,
L: Layer<Routes>,
L::Service: Service<Request<Body>, Response = Response<ResBody>> + Clone + Send + 'static,
<<L as Layer<Routes<A, B, Request<Body>>>>::Service as Service<Request<Body>>>::Future:
Send + 'static,
<<L as Layer<Routes<A, B, Request<Body>>>>::Service as Service<Request<Body>>>::Error:
Into<crate::Error> + Send,
<<L as Layer<Routes>>::Service as Service<Request<Body>>>::Future: Send + 'static,
<<L as Layer<Routes>>::Service as Service<Request<Body>>>::Error: Into<crate::Error> + Send,
ResBody: http_body::Body<Data = Bytes> + Send + 'static,
ResBody::Error: Into<crate::Error>,
{
@@ -658,12 +561,10 @@ where
signal: F,
) -> Result<(), super::Error>
where
L: Layer<Routes<A, B, Request<Body>>>,
L: Layer<Routes>,
L::Service: Service<Request<Body>, Response = Response<ResBody>> + Clone + Send + 'static,
<<L as Layer<Routes<A, B, Request<Body>>>>::Service as Service<Request<Body>>>::Future:
Send + 'static,
<<L as Layer<Routes<A, B, Request<Body>>>>::Service as Service<Request<Body>>>::Error:
Into<crate::Error> + Send,
<<L as Layer<Routes>>::Service as Service<Request<Body>>>::Future: Send + 'static,
<<L as Layer<Routes>>::Service as Service<Request<Body>>>::Error: Into<crate::Error> + Send,
ResBody: http_body::Body<Data = Bytes> + Send + 'static,
ResBody::Error: Into<crate::Error>,
{
@@ -687,12 +588,10 @@ where
IO: AsyncRead + AsyncWrite + Connected + Unpin + Send + 'static,
IO::ConnectInfo: Clone + Send + Sync + 'static,
IE: Into<crate::Error>,
L: Layer<Routes<A, B, Request<Body>>>,
L: Layer<Routes>,
L::Service: Service<Request<Body>, Response = Response<ResBody>> + Clone + Send + 'static,
<<L as Layer<Routes<A, B, Request<Body>>>>::Service as Service<Request<Body>>>::Future:
Send + 'static,
<<L as Layer<Routes<A, B, Request<Body>>>>::Service as Service<Request<Body>>>::Error:
Into<crate::Error> + Send,
<<L as Layer<Routes>>::Service as Service<Request<Body>>>::Future: Send + 'static,
<<L as Layer<Routes>>::Service as Service<Request<Body>>>::Error: Into<crate::Error> + Send,
ResBody: http_body::Body<Data = Bytes> + Send + 'static,
ResBody::Error: Into<crate::Error>,
{
@@ -722,12 +621,10 @@ where
IO::ConnectInfo: Clone + Send + Sync + 'static,
IE: Into<crate::Error>,
F: Future<Output = ()>,
L: Layer<Routes<A, B, Request<Body>>>,
L: Layer<Routes>,
L::Service: Service<Request<Body>, Response = Response<ResBody>> + Clone + Send + 'static,
<<L as Layer<Routes<A, B, Request<Body>>>>::Service as Service<Request<Body>>>::Future:
Send + 'static,
<<L as Layer<Routes<A, B, Request<Body>>>>::Service as Service<Request<Body>>>::Error:
Into<crate::Error> + Send,
<<L as Layer<Routes>>::Service as Service<Request<Body>>>::Future: Send + 'static,
<<L as Layer<Routes>>::Service as Service<Request<Body>>>::Error: Into<crate::Error> + Send,
ResBody: http_body::Body<Data = Bytes> + Send + 'static,
ResBody::Error: Into<crate::Error>,
{
@@ -737,19 +634,16 @@ where
}
/// Create a tower service out of a router.
pub fn into_service<ResBody>(self) -> RouterService<L::Service>
pub fn into_service<ResBody>(self) -> L::Service
where
L: Layer<Routes<A, B, Request<Body>>>,
L: Layer<Routes>,
L::Service: Service<Request<Body>, Response = Response<ResBody>> + Clone + Send + 'static,
<<L as Layer<Routes<A, B, Request<Body>>>>::Service as Service<Request<Body>>>::Future:
Send + 'static,
<<L as Layer<Routes<A, B, Request<Body>>>>::Service as Service<Request<Body>>>::Error:
Into<crate::Error> + Send,
<<L as Layer<Routes>>::Service as Service<Request<Body>>>::Future: Send + 'static,
<<L as Layer<Routes>>::Service as Service<Request<Body>>>::Error: Into<crate::Error> + Send,
ResBody: http_body::Body<Data = Bytes> + Send + 'static,
ResBody::Error: Into<crate::Error>,
{
let inner = self.server.layer.layer(self.routes);
RouterService { inner }
self.server.layer.layer(self.routes)
}
}
@@ -905,30 +799,3 @@ where
future::ready(Ok(svc))
}
}
#[derive(Default, Clone, Debug)]
#[doc(hidden)]
pub struct Unimplemented {
_p: (),
}
impl Service<Request<Body>> for Unimplemented {
type Response = Response<BoxBody>;
type Error = crate::Error;
type Future = future::Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Ok(()).into()
}
fn call(&mut self, _req: Request<Body>) -> Self::Future {
future::ok(
http::Response::builder()
.status(200)
.header("grpc-status", "12")
.header("content-type", "application/grpc")
.body(crate::body::empty_body())
.unwrap(),
)
}
}
+1 -1
View File
@@ -16,9 +16,9 @@ pub(crate) use self::connector::connector;
pub(crate) use self::discover::DynamicServiceStream;
pub(crate) use self::grpc_timeout::GrpcTimeout;
pub(crate) use self::io::ServerIo;
pub(crate) use self::router::{Or, Routes};
#[cfg(feature = "tls")]
pub(crate) use self::tls::{TlsAcceptor, TlsConnector};
pub(crate) use self::user_agent::UserAgent;
pub use self::grpc_timeout::TimeoutExpired;
pub use self::router::Routes;
+70 -112
View File
@@ -1,132 +1,90 @@
use futures_util::{
future::Either,
future::{MapErr, TryFutureExt},
use crate::{
body::{boxed, BoxBody},
transport::NamedService,
};
use axum::handler::Handler;
use http::{Request, Response};
use hyper::Body;
use pin_project::pin_project;
use std::{
fmt,
sync::Arc,
convert::Infallible,
future::Future,
pin::Pin,
task::{Context, Poll},
};
use tower::ServiceExt;
use tower_service::Service;
#[doc(hidden)]
#[derive(Debug)]
pub struct Routes<A, B, Request> {
routes: Or<A, B, Request>,
/// A [`Service`] router.
#[derive(Debug, Default, Clone)]
pub struct Routes {
router: axum::Router,
}
impl<A, B, Request> Routes<A, B, Request> {
pub(crate) fn new(
predicate: impl Fn(&Request) -> bool + Send + Sync + 'static,
a: A,
b: B,
) -> Self {
let routes = Or::new(predicate, a, b);
Self { routes }
}
}
impl<A, B, Request> Routes<A, B, Request> {
pub(crate) fn push<C>(
self,
predicate: impl Fn(&Request) -> bool + Send + Sync + 'static,
route: C,
) -> Routes<C, Or<A, B, Request>, Request> {
let routes = Or::new(predicate, route, self.routes);
Routes { routes }
}
}
impl<A, B, Request> Service<Request> for Routes<A, B, Request>
where
A: Service<Request>,
A::Future: Send + 'static,
A::Error: Into<crate::Error>,
B: Service<Request, Response = A::Response>,
B::Future: Send + 'static,
B::Error: Into<crate::Error>,
{
type Response = A::Response;
type Error = crate::Error;
type Future = <Or<A, B, Request> as Service<Request>>::Future;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Ok(()).into()
}
fn call(&mut self, req: Request) -> Self::Future {
self.routes.call(req)
}
}
impl<A: Clone, B: Clone, Request> Clone for Routes<A, B, Request> {
fn clone(&self) -> Self {
Self {
routes: self.routes.clone(),
}
}
}
#[doc(hidden)]
pub struct Or<A, B, Request> {
predicate: Arc<dyn Fn(&Request) -> bool + Send + Sync + 'static>,
a: A,
b: B,
}
impl<A, B, Request> Or<A, B, Request> {
pub(crate) fn new<F>(predicate: F, a: A, b: B) -> Self
impl Routes {
pub(crate) fn new<S>(svc: S) -> Self
where
F: Fn(&Request) -> bool + Send + Sync + 'static,
S: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible>
+ NamedService
+ Clone
+ Send
+ 'static,
S::Future: Send + 'static,
S::Error: Into<crate::Error> + Send,
{
let predicate = Arc::new(predicate);
Self { predicate, a, b }
let router = axum::Router::new().fallback(unimplemented.into_service());
Self { router }.add_service(svc)
}
pub(crate) fn add_service<S>(mut self, svc: S) -> Self
where
S: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible>
+ NamedService
+ Clone
+ Send
+ 'static,
S::Future: Send + 'static,
S::Error: Into<crate::Error> + Send,
{
let svc = svc.map_response(|res| res.map(axum::body::boxed));
self.router = self.router.route(&format!("/{}/*rest", S::NAME), svc);
self
}
}
impl<A, B, Request> Service<Request> for Or<A, B, Request>
where
A: Service<Request>,
A::Future: Send + 'static,
A::Error: Into<crate::Error>,
B: Service<Request, Response = A::Response>,
B::Future: Send + 'static,
B::Error: Into<crate::Error>,
{
type Response = A::Response;
async fn unimplemented() -> impl axum::response::IntoResponse {
let status = http::StatusCode::OK;
let headers =
axum::response::Headers([("grpc-status", "12"), ("content-type", "application/grpc")]);
(status, headers)
}
impl Service<Request<Body>> for Routes {
type Response = Response<BoxBody>;
type Error = crate::Error;
type Future = RoutesFuture;
#[allow(clippy::type_complexity)]
type Future = Either<
MapErr<A::Future, fn(A::Error) -> crate::Error>,
MapErr<B::Future, fn(B::Error) -> crate::Error>,
>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Ok(()).into()
#[inline]
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request) -> Self::Future {
if (self.predicate)(&req) {
Either::Left(self.a.call(req).map_err(|e| e.into()))
} else {
Either::Right(self.b.call(req).map_err(|e| e.into()))
fn call(&mut self, req: Request<Body>) -> Self::Future {
RoutesFuture(self.router.call(req))
}
}
#[pin_project]
#[derive(Debug)]
pub struct RoutesFuture(#[pin] axum::routing::future::RouterFuture<Body>);
impl Future for RoutesFuture {
type Output = Result<Response<BoxBody>, crate::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match futures_util::ready!(self.project().0.poll(cx)) {
Ok(res) => Ok(res.map(boxed)).into(),
Err(err) => match err {},
}
}
}
impl<A: Clone, B: Clone, Request> Clone for Or<A, B, Request> {
fn clone(&self) -> Self {
Self {
predicate: self.predicate.clone(),
a: self.a.clone(),
b: self.b.clone(),
}
}
}
impl<A, B, Request> fmt::Debug for Or<A, B, Request> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Or {{ .. }}")
}
}