From 27d2dc09e13bfc2f76ba3a17775728f6e25e2128 Mon Sep 17 00:00:00 2001 From: Ihor Vovk Date: Tue, 3 Dec 2024 11:23:20 +0100 Subject: [PATCH] Support configurable paths prefixes for Connect routes --- .../ConnectRouteBuilder.scala | 10 ++++--- .../ivovk/connect_rpc_scala/HttpTest.scala | 26 +++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) 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 0389acb..3aff336 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 @@ -5,7 +5,7 @@ import cats.effect.{Async, Resource} import cats.implicits.* import io.grpc.{ManagedChannelBuilder, ServerBuilder, ServerServiceDefinition} import org.http4s.dsl.Http4sDsl -import org.http4s.{HttpApp, HttpRoutes, Method} +import org.http4s.{HttpApp, HttpRoutes, Method, Uri} import org.ivovk.connect_rpc_scala.grpc.* import org.ivovk.connect_rpc_scala.http.* import org.ivovk.connect_rpc_scala.http.QueryParams.* @@ -35,6 +35,7 @@ case class ConnectRouteBuilder[F[_] : Async] private( jsonPrinterConfigurator: Endo[Printer] = identity, serverConfigurator: Endo[ServerBuilder[_]] = identity, channelConfigurator: Endo[ManagedChannelBuilder[_]] = identity, + pathPrefix: Uri.Path = Uri.Path.Root, executor: Executor = ExecutionContext.global, waitForShutdown: Duration = 5.seconds, treatTrailersAsHeaders: Boolean = true, @@ -51,6 +52,9 @@ case class ConnectRouteBuilder[F[_] : Async] private( def withChannelConfigurator(method: Endo[ManagedChannelBuilder[_]]): ConnectRouteBuilder[F] = copy(channelConfigurator = method) + def withPathPrefix(path: Uri.Path): ConnectRouteBuilder[F] = + copy(pathPrefix = path) + def withExecutor(executor: Executor): ConnectRouteBuilder[F] = copy(executor = executor) @@ -106,12 +110,12 @@ case class ConnectRouteBuilder[F[_] : Async] private( ) HttpRoutes.of[F] { - case req@Method.GET -> Root / serviceName / methodName :? EncodingQP(contentType) +& MessageQP(message) => + case req@Method.GET -> pathPrefix / serviceName / methodName :? EncodingQP(contentType) +& MessageQP(message) => val grpcMethod = MethodName(serviceName, methodName) val entity = RequestEntity[F](message, req.headers) handler.handle(Method.GET, contentType.some, entity, grpcMethod) - case req@Method.POST -> Root / serviceName / methodName => + case req@Method.POST -> pathPrefix / serviceName / methodName => val grpcMethod = MethodName(serviceName, methodName) val contentType = req.contentType.map(_.mediaType) val entity = RequestEntity[F](req) diff --git a/core/src/test/scala/org/ivovk/connect_rpc_scala/HttpTest.scala b/core/src/test/scala/org/ivovk/connect_rpc_scala/HttpTest.scala index 8f807c3..c587e79 100644 --- a/core/src/test/scala/org/ivovk/connect_rpc_scala/HttpTest.scala +++ b/core/src/test/scala/org/ivovk/connect_rpc_scala/HttpTest.scala @@ -90,4 +90,30 @@ class HttpTest extends AnyFunSuite, Matchers { .unsafeRunSync() } + test("support path prefixes") { + val service = TestService.bindService(TestServiceImpl, ExecutionContext.global) + + ConnectRouteBuilder.forService[IO](service) + .withPathPrefix(Root / "connect") + .build + .flatMap { app => + val client = Client.fromHttpApp(app) + + client.run( + Request[IO](Method.POST, uri"/connect/org.ivovk.connect_rpc_scala.test.TestService/Add") + .withEntity(""" { "a": 1, "b": 2} """) + ) + } + .use { response => + for { + body <- response.as[String] + status <- response.status.pure[IO] + } yield { + assert(body == """{"sum":3}""") + assert(status == Status.Ok) + } + } + .unsafeRunSync() + } + }