use std::convert::Infallible;
use std::future::Future;
use std::pin::Pin;
use std::task::{ready, Context, Poll};
use axum::extract::Request;
use axum::response::Response;
use pin_project_lite::pin_project;
use tower::Service;
use super::{http_to_grpc_code, BoxedService, Rejection};
use crate::upstream::{grpc_protocol, trailers_only, GrpcProtocol};
#[derive(Clone)]
pub(crate) struct GrpcRejections {
pub(crate) inner: BoxedService,
pub(crate) protocol: GrpcProtocol,
}
impl Service<Request> for GrpcRejections {
type Response = Response;
type Error = Infallible;
type Future = RejectionFuture;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Infallible>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, request: Request) -> Self::Future {
RejectionFuture {
future: self.inner.call(request),
protocol: self.protocol,
}
}
}
pin_project! {
pub(crate) struct RejectionFuture {
#[pin]
future: <BoxedService as Service<Request>>::Future,
protocol: GrpcProtocol,
}
}
impl Future for RejectionFuture {
type Output = Result<Response, Infallible>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let response = ready!(this.future.poll(cx))?;
Poll::Ready(Ok(in_grpc(response, *this.protocol)))
}
}
const BODY_HEADERS: [http::HeaderName; 3] = [
http::header::CONTENT_TYPE,
http::header::CONTENT_LENGTH,
http::header::TRANSFER_ENCODING,
];
fn in_grpc(response: Response, protocol: GrpcProtocol) -> Response {
let (code, message) = match response.extensions().get::<Rejection>() {
Some(rejection) => (rejection.code, rejection.message.to_string()),
None if grpc_protocol(response.headers()).is_some() => return response,
None => (
http_to_grpc_code(response.status()),
response
.status()
.canonical_reason()
.unwrap_or_default()
.to_string(),
),
};
let (parts, _) = response.into_parts();
let mut answer = trailers_only(tonic::Status::new(code, message), protocol);
let headers = answer.headers_mut();
let mut previous = None;
for (name, value) in parts.headers {
if let Some(name) = name {
previous = Some(name);
}
let Some(name) = &previous else { continue };
if BODY_HEADERS.contains(name) {
continue;
}
headers.append(name.clone(), value);
}
answer
}