Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StatusException metadata trailers not propagated to client #547

Open
Gregory-Berkman-Imprivata opened this issue Aug 22, 2023 · 8 comments

Comments

@Gregory-Berkman-Imprivata

Metadata trailers are not being received when a request fails with a StatusException in zio-grpc 0.6.0 .

I have replicated this in the HelloWorldServer and HelloWorldClient

HelloWorldServer:

package zio_grpc.examples.helloworld

import io.grpc.Metadata.BinaryMarshaller
import io.grpc.{Metadata, Status, StatusException}
import scalapb.zio_grpc.ServerMain
import scalapb.zio_grpc.ServiceList
import zio._
import zio.Console._
import io.grpc.examples.helloworld.helloworld.ZioHelloworld.Greeter
import io.grpc.examples.helloworld.helloworld.{HelloReply, HelloRequest}

object GreeterImpl extends Greeter {
  def sayHello(
      request: HelloRequest
  ): ZIO[Any, StatusException, HelloReply] = {

    val metadataKey = Metadata.Key.of("foo-bin", new BinaryMarshaller[String] {
      override def toBytes(value: String): Array[Byte] = value.getBytes

      override def parseBytes(serialized: Array[Byte]): String = new String(serialized)
    })
    val metadata = new Metadata()
    metadata.put(metadataKey, "bar")

    (printLine(s"Got request: $request").orDie zipRight
      ZIO.fail(new StatusException(Status.INTERNAL, metadata)))
      .tapError(ex => printLine(ex.getTrailers.get(metadataKey)).orDie)
  }
}

object HelloWorldServer extends ServerMain {
  def services: ServiceList[Any] = ServiceList.add(GreeterImpl)
}

HelloWorldClient:

package zio_grpc.examples.helloworld

import io.grpc.examples.helloworld.helloworld.ZioHelloworld.GreeterClient
import io.grpc.examples.helloworld.helloworld.HelloRequest
import io.grpc.{ManagedChannelBuilder, Metadata}
import io.grpc.Metadata.BinaryMarshaller
import zio.Console._
import scalapb.zio_grpc.ZManagedChannel
import zio._

object HelloWorldClient extends zio.ZIOAppDefault {
  val clientLayer: Layer[Throwable, GreeterClient] =
    GreeterClient.live(
      ZManagedChannel(
        ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext()
      )
    )

  def myAppLogic = {
    val metadataKey = Metadata.Key.of("foo-bin", new BinaryMarshaller[String] {
      override def toBytes(value: String): Array[Byte] = value.getBytes

      override def parseBytes(serialized: Array[Byte]): String = new String(serialized)
    })

    for {
      r <- GreeterClient.sayHello(HelloRequest("World")).either
      _ <- r.fold(ex => printLine(s"${ex.getTrailers.get(metadataKey)}-${ex.getStatus}"), s => printLine(s.message))
    } yield ()
  }

  final def run =
    myAppLogic.provideLayer(clientLayer).exitCode
}

Server output:

Server is running on port 9000. Press Ctrl-C to stop.
Got request: HelloRequest(World,UnknownFieldSet(Map()))
bar

Expected Client output:

bar-Status{code=INTERNAL, description=null, cause=null}

Actual Client output:

null-Status{code=INTERNAL, description=null, cause=null}

As you can see the client receives null within the exception trailer metadata.

@thesamet
Copy link
Contributor

thesamet commented Aug 22, 2023 via email

@Gregory-Berkman-Imprivata
Copy link
Author

i can try. I havent contributed yet and am not too familiar with the code. I really need this feature though.

@thesamet
Copy link
Contributor

thesamet commented Aug 22, 2023 via email

@Gregory-Berkman-Imprivata
Copy link
Author

I think I may have found a way to add the trailers to the metadata. I went to put up a PR to discuss but I do not have access permissions to push a branch.

@thesamet
Copy link
Contributor

You can send a PR by pushing a branch to your personal fork of this project.

@Gregory-Berkman-Imprivata
Copy link
Author

I figured out how to do this in my application so I am no longer blocked.

class PropagatingMetadataTrailersTransformer extends GTransform[RequestContext, StatusException, RequestContext, StatusException] {
  
    def effect[A](
      io: RequestContext => ZIO[Any, StatusException, A]
    ): (RequestContext => ZIO[Any, StatusException, A]) = { context => 
        io(context).tapError(ex => context.responseMetadata.wrap(_.merge(ex.getTrailers)))
    }
   
    ...
} 

I attempted to implement a similar solution in the PR but tests were failing. Any reason a solution like this could cause issues?

@thesamet
Copy link
Contributor

Not seeing an obvious issue with the workaround.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants