diff --git a/server/zio-http-server/src/main/scala/sttp/tapir/server/ziohttp/ZioHttpInterpreter.scala b/server/zio-http-server/src/main/scala/sttp/tapir/server/ziohttp/ZioHttpInterpreter.scala index 2561b0a3bd..a8d1789b7f 100644 --- a/server/zio-http-server/src/main/scala/sttp/tapir/server/ziohttp/ZioHttpInterpreter.scala +++ b/server/zio-http-server/src/main/scala/sttp/tapir/server/ziohttp/ZioHttpInterpreter.scala @@ -87,30 +87,25 @@ trait ZioHttpInterpreter[R] { case _ => false } - val pattern = if (hasPath) { + val pathPrefixPattern = if (hasPath) { val initialPattern = RoutePattern(Method.ANY, PathCodec.empty).asInstanceOf[RoutePattern[Any]] - // The second tuple parameter specifies if PathCodec.trailing has already been added to the route's pattern - - // it can only be added once. It's possible that an endpoint contains both ExtractFromRequest & PathsCapture, - // which would cause it to be added twice. - inputs - .foldLeft((initialPattern, false)) { case ((p, trailingAdded), component) => + val base = inputs + .foldLeft(initialPattern) { case (p, component) => component match { - case i: EndpointInput.PathCapture[_] => - ((p / PathCodec.string(i.name.getOrElse("?"))).asInstanceOf[RoutePattern[Any]], trailingAdded) - case _: EndpointInput.ExtractFromRequest[_] if !trailingAdded => - ((p / PathCodec.trailing).asInstanceOf[RoutePattern[Any]], true) - case _: EndpointInput.PathsCapture[_] if !trailingAdded => ((p / PathCodec.trailing).asInstanceOf[RoutePattern[Any]], true) - case i: EndpointInput.FixedPath[_] => (p / PathCodec.literal(i.s), trailingAdded) - case _ => (p, trailingAdded) + case i: EndpointInput.PathCapture[_] => (p / PathCodec.string(i.name.getOrElse("?"))).asInstanceOf[RoutePattern[Any]] + case i: EndpointInput.FixedPath[_] => p / PathCodec.literal(i.s) + case _ => p } } - ._1 + // because we capture the path prefix, we add a matcher for arbitrary other path components (which might be + // handled by tapir's `paths` or `extractFromRequest`) + base / PathCodec.trailing } else { // if there are no path inputs, we return a catch-all RoutePattern(Method.ANY, PathCodec.trailing).asInstanceOf[RoutePattern[Any]] } - Route.handled(pattern)(Handler.fromFunctionHandler { (request: Request) => handleRequest(request, sesForPathTemplate) }) + Route.handled(pathPrefixPattern)(Handler.fromFunctionHandler { (request: Request) => handleRequest(request, sesForPathTemplate) }) } Routes(Chunk.fromIterable(handlers)) diff --git a/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpServerTest.scala b/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpServerTest.scala index e7789272b4..9e266d72d8 100644 --- a/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpServerTest.scala +++ b/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpServerTest.scala @@ -163,6 +163,24 @@ class ZioHttpServerTest extends TestSuite { Unsafe.unsafe(implicit u => r.unsafe.runToFuture(test)) }, + // https://github.com/softwaremill/tapir/issues/3907 + Test("extractFromRequest in the middle") { + val ep = endpoint.get + .in(path[String]) + .in(extractFromRequest(_.method)) + .in("test" / path[String]) + .out(stringBody) + .zServerLogic[Any](_ => ZIO.succeed("works")) + val route = ZioHttpInterpreter().toHttp(ep) + + val test: UIO[Assertion] = route + .runZIO(Request.get(url = URL(Path.empty / "p1" / "test" / "p2"))) + .flatMap(response => response.body.asString) + .map(_ shouldBe "works") + .catchAll(_ => ZIO.succeed[Assertion](fail("Unable to extract body from Http response"))) + + Unsafe.unsafe(implicit u => r.unsafe.runToFuture(test)) + }, // https://github.com/softwaremill/tapir/issues/2849 Test("Streaming works through the stub backend") { // given