diff --git a/core/src/main/scala/org/ivovk/connect_rpc_scala/ConnectRouteBuilder.scala b/core/src/main/scala/org/ivovk/connect_rpc_scala/ConnectRouteBuilder.scala index 2879434..2f320de 100644 --- a/core/src/main/scala/org/ivovk/connect_rpc_scala/ConnectRouteBuilder.scala +++ b/core/src/main/scala/org/ivovk/connect_rpc_scala/ConnectRouteBuilder.scala @@ -8,10 +8,10 @@ import io.grpc.{ManagedChannelBuilder, ServerBuilder, ServerServiceDefinition} import org.http4s.dsl.Http4sDsl import org.http4s.{HttpApp, HttpRoutes, MediaType, Method, Response, Uri} import org.ivovk.connect_rpc_scala.grpc.* +import org.ivovk.connect_rpc_scala.grpc.MergingBuilder.* import org.ivovk.connect_rpc_scala.http.* import org.ivovk.connect_rpc_scala.http.QueryParams.* import org.ivovk.connect_rpc_scala.http.codec.* -import org.ivovk.connect_rpc_scala.syntax.all.* import scalapb.{GeneratedMessage as Message, GeneratedMessageCompanion as Companion} import java.util.concurrent.Executor @@ -182,7 +182,7 @@ final class ConnectRouteBuilder[F[_] : Async] private( val queryMessage = jsonCodec.parser.fromJson[Message](queryJson) transcodingHandler.handleUnary( - bodyMessage.concat(pathMessage, queryMessage), + bodyMessage.merge(pathMessage).merge(queryMessage).build, req.headers, method ) diff --git a/core/src/main/scala/org/ivovk/connect_rpc_scala/grpc/MergingBuilder.scala b/core/src/main/scala/org/ivovk/connect_rpc_scala/grpc/MergingBuilder.scala new file mode 100644 index 0000000..f8eb550 --- /dev/null +++ b/core/src/main/scala/org/ivovk/connect_rpc_scala/grpc/MergingBuilder.scala @@ -0,0 +1,45 @@ +package org.ivovk.connect_rpc_scala.grpc + +import com.google.protobuf.ByteString +import scalapb.{GeneratedMessage as Message, GeneratedMessageCompanion as Companion} + +object MergingBuilder { + extension [T <: Message](t: T) { + def merge(other: T)(using Companion[T]): MergingBuilder[T] = + SingleMergingBuilder(t).merge(other) + } +} + +sealed trait MergingBuilder[T <: Message] { + def merge(other: T): MergingBuilder[T] + + def build: T +} + +private class SingleMergingBuilder[T <: Message](t: T)(using cmp: Companion[T]) extends MergingBuilder[T] { + override def merge(other: T): MergingBuilder[T] = { + val empty = cmp.defaultInstance + + if other == empty then this + else if t == empty then SingleMergingBuilder(other) + else ListMergingBuilder(other :: t :: Nil) + } + + override def build: T = t +} + +private class ListMergingBuilder[T <: Message](ts: List[T])(using cmp: Companion[T]) extends MergingBuilder[T] { + override def merge(other: T): MergingBuilder[T] = { + val empty = cmp.defaultInstance + + if other == empty then this + else ListMergingBuilder(other :: ts) + } + + override def build: T = { + val output = ByteString.newOutput(ts.foldLeft(0)(_ + _.serializedSize)) + ts.reverse.foreach(_.writeTo(output)) + output.close() + cmp.parseFrom(output.toByteString.newCodedInput()) + } +} diff --git a/core/src/main/scala/org/ivovk/connect_rpc_scala/syntax/all.scala b/core/src/main/scala/org/ivovk/connect_rpc_scala/syntax/all.scala index c4b126b..d2d40e9 100644 --- a/core/src/main/scala/org/ivovk/connect_rpc_scala/syntax/all.scala +++ b/core/src/main/scala/org/ivovk/connect_rpc_scala/syntax/all.scala @@ -1,9 +1,8 @@ package org.ivovk.connect_rpc_scala.syntax -import com.google.protobuf.ByteString import io.grpc.{StatusException, StatusRuntimeException} import org.ivovk.connect_rpc_scala.grpc.GrpcHeaders -import scalapb.{GeneratedMessage, GeneratedMessageCompanion} +import scalapb.GeneratedMessage object all extends ExceptionSyntax, ProtoMappingsSyntax @@ -34,20 +33,6 @@ trait ExceptionSyntax { trait ProtoMappingsSyntax { extension [T <: GeneratedMessage](t: T) { - def concat(other: T, more: T*): T = { - val cmp = t.companion.asInstanceOf[GeneratedMessageCompanion[T]] - val empty = cmp.defaultInstance - - val els = (t :: other :: more.toList).filter(_ != empty) - - els match - case Nil => empty - case el :: Nil => el - case _ => - val is = els.foldLeft(ByteString.empty)(_ concat _.toByteString).newCodedInput() - cmp.parseFrom(is) - } - def toProtoAny: com.google.protobuf.any.Any = { com.google.protobuf.any.Any( typeUrl = "type.googleapis.com/" + t.companion.scalaDescriptor.fullName, diff --git a/core/src/test/scala/org/ivovk/connect_rpc_scala/grpc/MergingBuilderTest.scala b/core/src/test/scala/org/ivovk/connect_rpc_scala/grpc/MergingBuilderTest.scala new file mode 100644 index 0000000..2404a39 --- /dev/null +++ b/core/src/test/scala/org/ivovk/connect_rpc_scala/grpc/MergingBuilderTest.scala @@ -0,0 +1,15 @@ +package org.ivovk.connect_rpc_scala.grpc + +import org.scalatest.funsuite.AnyFunSuite +import test.HttpCommunicationTest.AddRequest + +class MergingBuilderTest extends AnyFunSuite { + import MergingBuilder.* + + test("merges three messages") { + val merge = AddRequest(a = 1).merge(AddRequest(b = 2)).merge(AddRequest(a = 3)).build + + assert(merge.a == 3) + assert(merge.b == 2) + } +}