Skip to content

Commit

Permalink
[transcoding] Better message merging syntax/implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
igor-vovk committed Dec 14, 2024
1 parent 1fbc2f0 commit fc2e8ea
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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] =
new SingleMessageMergingBuilder(t).merge(other)
}
}

sealed trait MergingBuilder[T <: Message] {
def merge(other: T): MergingBuilder[T]

def build: T
}

private class SingleMessageMergingBuilder[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 SingleMessageMergingBuilder(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())
}
}
17 changes: 1 addition & 16 deletions core/src/main/scala/org/ivovk/connect_rpc_scala/syntax/all.scala
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit fc2e8ea

Please sign in to comment.