This is an automated email from the ASF dual-hosted git repository.

fanningpj pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-pekko-grpc.git


The following commit(s) were added to refs/heads/main by this push:
     new d6503802 make sure trailers are present in StatusRuntimeException 
(#230)
d6503802 is described below

commit d65038020103c6d48aff3ed5a5085fb584e88611
Author: João Ferreira <[email protected]>
AuthorDate: Sat Feb 24 16:44:51 2024 +0000

    make sure trailers are present in StatusRuntimeException (#230)
---
 .../pekko/grpc/internal/PekkoHttpClientUtils.scala | 23 +++++++++---------
 .../grpc/internal/PekkoHttpClientUtilsSpec.scala   | 27 +++++++++++++++++++---
 2 files changed, 36 insertions(+), 14 deletions(-)

diff --git 
a/runtime/src/main/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtils.scala
 
b/runtime/src/main/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtils.scala
index 01215c2b..b78bc7f5 100644
--- 
a/runtime/src/main/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtils.scala
+++ 
b/runtime/src/main/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtils.scala
@@ -24,16 +24,10 @@ import pekko.annotation.InternalApi
 import pekko.event.LoggingAdapter
 import pekko.grpc.GrpcProtocol.GrpcProtocolReader
 import pekko.grpc.{ GrpcClientSettings, GrpcResponseMetadata, 
GrpcSingleResponse, ProtobufSerializer }
+import pekko.grpc.scaladsl.StringEntry
 import pekko.http.scaladsl.model.HttpEntity.{ Chunk, Chunked, LastChunk, 
Strict }
 import pekko.http.scaladsl.{ ClientTransport, ConnectionContext, Http }
-import pekko.http.scaladsl.model.{
-  AttributeKey,
-  HttpHeader,
-  HttpRequest,
-  HttpResponse,
-  RequestResponseAssociation,
-  Uri
-}
+import pekko.http.scaladsl.model._
 import pekko.http.scaladsl.settings.ClientConnectionSettings
 import pekko.stream.{ Materializer, OverflowStrategy }
 import pekko.stream.scaladsl.{ Keep, Sink, Source }
@@ -232,7 +226,12 @@ object PekkoHttpClientUtils {
                         .watchTermination()((_, done) =>
                           done.onComplete(_ => 
trailerPromise.trySuccess(immutable.Seq.empty)))
                     case Strict(_, data) =>
-                      trailerPromise.success(immutable.Seq.empty)
+                      val rawTrailers =
+                        
response.attribute(AttributeKeys.trailer).map(_.headers).getOrElse(immutable.Seq.empty)
+                      val trailers = rawTrailers.map(h => 
HttpHeader.parse(h._1, h._2)).collect {
+                        case HttpHeader.ParsingResult.Ok(header, _) => header
+                      }
+                      trailerPromise.success(trailers)
                       Source.single[ByteString](data)
                     case _ =>
                       response.entity.discardBytes()
@@ -286,12 +285,14 @@ object PekkoHttpClientUtils {
 
   private def mapToStatusException(response: HttpResponse, trailers: 
Seq[HttpHeader]): StatusRuntimeException = {
     val allHeaders = response.headers ++ trailers
+    val metadata: io.grpc.Metadata =
+      new MetadataImpl(allHeaders.map(h => (h.name, 
StringEntry(h.value))).toList).toGoogleGrpcMetadata()
     allHeaders.find(_.name == "grpc-status").map(_.value) match {
       case None =>
-        new StatusRuntimeException(mapHttpStatus(response).withDescription("No 
grpc-status found"))
+        new StatusRuntimeException(mapHttpStatus(response).withDescription("No 
grpc-status found"), metadata)
       case Some(statusCode) =>
         val description = allHeaders.find(_.name == 
"grpc-message").map(_.value)
-        new 
StatusRuntimeException(Status.fromCodeValue(statusCode.toInt).withDescription(description.orNull))
+        new 
StatusRuntimeException(Status.fromCodeValue(statusCode.toInt).withDescription(description.orNull),
 metadata)
     }
   }
 
diff --git 
a/runtime/src/test/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtilsSpec.scala
 
b/runtime/src/test/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtilsSpec.scala
index 389b9d82..0915b20d 100644
--- 
a/runtime/src/test/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtilsSpec.scala
+++ 
b/runtime/src/test/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtilsSpec.scala
@@ -19,12 +19,12 @@ import scala.concurrent.duration._
 import org.apache.pekko
 import pekko.actor.ActorSystem
 import pekko.http.scaladsl.model.HttpEntity.Strict
-import pekko.http.scaladsl.model.HttpResponse
+import pekko.http.scaladsl.model._
 import pekko.http.scaladsl.model.StatusCodes._
 import pekko.http.scaladsl.model.headers.RawHeader
 import pekko.testkit.TestKit
 import pekko.util.ByteString
-import io.grpc.{ Status, StatusRuntimeException }
+import io.grpc.{ Metadata, Status, StatusRuntimeException }
 import org.scalatest.concurrent.ScalaFutures
 import org.scalatest.matchers.should.Matchers
 import org.scalatest.time.Span
@@ -47,12 +47,33 @@ class PekkoHttpClientUtilsSpec extends 
TestKit(ActorSystem()) with AnyWordSpecLi
     }
 
     "map a strict 200 response with non-0 gRPC error code to a failed stream" 
in {
+      val responseHeaders = List(RawHeader("grpc-status", "9"), 
RawHeader("custom-key", "custom-value-in-header"))
+      val response =
+        Future.successful(HttpResponse(OK, responseHeaders, 
Strict(GrpcProtocolNative.contentType, ByteString.empty)))
+      val source = PekkoHttpClientUtils.responseToSource(response, null)
+
+      val failure = source.run().failed.futureValue
+      failure.asInstanceOf[StatusRuntimeException].getStatus.getCode should 
be(Status.Code.FAILED_PRECONDITION)
+      failure.asInstanceOf[StatusRuntimeException].getTrailers.get(key) should 
be("custom-value-in-header")
+    }
+
+    "map a strict 200 response with non-0 gRPC error code with a trailer to a 
failed stream with trailer metadata" in {
+      val responseHeaders = List(RawHeader("grpc-status", "9"))
+      val responseTrailers = Trailer(RawHeader("custom-key", 
"custom-trailer-value") :: Nil)
       val response = Future.successful(
-        HttpResponse(OK, List(RawHeader("grpc-status", "9")), 
Strict(GrpcProtocolNative.contentType, ByteString.empty)))
+        new HttpResponse(
+          OK,
+          responseHeaders,
+          Map.empty[AttributeKey[_], Any].updated(AttributeKeys.trailer, 
responseTrailers),
+          Strict(GrpcProtocolNative.contentType, ByteString.empty),
+          HttpProtocols.`HTTP/1.1`))
       val source = PekkoHttpClientUtils.responseToSource(response, null)
 
       val failure = source.run().failed.futureValue
       failure.asInstanceOf[StatusRuntimeException].getStatus.getCode should 
be(Status.Code.FAILED_PRECONDITION)
+      failure.asInstanceOf[StatusRuntimeException].getTrailers.get(key) should 
be("custom-trailer-value")
     }
+
+    lazy val key = Metadata.Key.of("custom-key", 
Metadata.ASCII_STRING_MARSHALLER)
   }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to