From 89e61f261e024b8a9d96fc5c68f08445ebafa30c Mon Sep 17 00:00:00 2001 From: Boris Smidt Date: Tue, 11 Jan 2022 18:19:25 +0100 Subject: [PATCH 001/120] #1737 Input annotated class can be empty. test still fails --- .../generic/internal/EndpointAnnotationsMacro.scala | 7 +++---- .../internal/EndpointInputAnnotationsMacro.scala | 12 ++++++++++++ .../internal/EndpointOutputAnnotationsMacro.scala | 4 +++- .../scala-2/sttp/tapir/internal/MapToMacro.scala | 9 ++++----- .../tapir/annotations/DeriveEndpointIOTest.scala | 9 +++++++++ 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala index 5f87b597f9..f8d924e3cc 100644 --- a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala @@ -31,9 +31,6 @@ abstract class EndpointAnnotationsMacro(val c: blackbox.Context) { protected val securitySchemeNameType = c.weakTypeOf[securitySchemeName] protected def validateCaseClass[A](util: CaseClassUtil[c.type, A]): Unit = { - if (util.fields.isEmpty) { - c.abort(c.enclosingPosition, "Case class must have at least one field") - } if (1 < util.fields.flatMap(bodyAnnotation).size) { c.abort(c.enclosingPosition, "No more than one body annotation is allowed") } @@ -113,8 +110,10 @@ abstract class EndpointAnnotationsMacro(val c: blackbox.Context) { } q"(t: $tupleType) => $className(..$ctorArgs)" - } else { + } else if (inputIdxToFieldIdx.size == 1) { q"(t: ${util.fields.head.info}) => $className(t)" + } else { + q"(t: ${}) => $className()" } } diff --git a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointInputAnnotationsMacro.scala b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointInputAnnotationsMacro.scala index b14116d41f..ab678d80e5 100644 --- a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointInputAnnotationsMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointInputAnnotationsMacro.scala @@ -23,6 +23,18 @@ class EndpointInputAnnotationsMacro(override val c: blackbox.Context) extends En val util = new CaseClassUtil[c.type, A](c, "request endpoint") validateCaseClass(util) + if (util.fields.isEmpty) { + val path = util.classSymbol.annotations + .map(_.tree) + .collectFirst { case Apply(Select(New(tree), _), List(arg)) if tree.tpe <:< endpointInput => arg } + + if (path.isEmpty) { + c.abort(c.enclosingPosition, s"case class is empty and not annotated with @endpointInput") + } else if (path.exists(tree => tree.toString().contains('{') || tree.toString().contains('}'))) { + c.abort(c.enclosingPosition, s"case class is empty but has path arguments in @endpointInput ${path.get}") + } + return c.Expr[EndpointInput[A]](q"stringToPath(${path.get}).mapTo[${util.classSymbol}]") + } val segments = util.classSymbol.annotations .map(_.tree) .collectFirst { case Apply(Select(New(tree), _), List(arg)) if tree.tpe <:< endpointInput => arg } diff --git a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointOutputAnnotationsMacro.scala b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointOutputAnnotationsMacro.scala index 5f0c1b7fc8..84da6716e7 100644 --- a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointOutputAnnotationsMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointOutputAnnotationsMacro.scala @@ -19,7 +19,9 @@ class EndpointOutputAnnotationsMacro(override val c: blackbox.Context) extends E def generateEndpointOutput[A: c.WeakTypeTag]: c.Expr[EndpointOutput[A]] = { val util = new CaseClassUtil[c.type, A](c, "response endpoint") validateCaseClass(util) - + if (util.fields.isEmpty) { + c.abort(c.enclosingPosition, "Case class must have at least one field") + } val outputs = util.fields map { field => val output = util .extractOptStringArgFromAnnotation(field, headerType) diff --git a/core/src/main/scala-2/sttp/tapir/internal/MapToMacro.scala b/core/src/main/scala-2/sttp/tapir/internal/MapToMacro.scala index 1e2ba68d15..e2b0bf97fa 100644 --- a/core/src/main/scala-2/sttp/tapir/internal/MapToMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/internal/MapToMacro.scala @@ -34,8 +34,9 @@ object MapToMacro { val caseClassUtil = new CaseClassUtil[c.type, CASE_CLASS](c, "mapTo mapping") val tupleType = weakTypeOf[TUPLE] val tupleTypeArgs = tupleType.dealias.typeArgs - - if (caseClassUtil.fields.size == 1) { + if (caseClassUtil.fields.size == 0) { + q"(t: ${tupleType.dealias}) => ${caseClassUtil.className}()" + } else if (caseClassUtil.fields.size == 1) { verifySingleFieldCaseClass(c)(caseClassUtil, tupleType) q"(t: ${tupleType.dealias}) => ${caseClassUtil.className}(t)" } else { @@ -50,10 +51,8 @@ object MapToMacro { val caseClassUtil = new CaseClassUtil[c.type, CASE_CLASS](c, "mapTo mapping") val tupleType = weakTypeOf[TUPLE] - if (caseClassUtil.fields.size == 1) { verifySingleFieldCaseClass(c)(caseClassUtil, tupleType) - } else { verifyCaseClassMatchesTuple(c)(caseClassUtil, tupleType, tupleType.dealias.typeArgs) } @@ -81,7 +80,7 @@ object MapToMacro { tupleTypeArgs: List[c.Type] ): Unit = { val tupleSymbol = tupleType.typeSymbol - if (!tupleSymbol.fullName.startsWith("scala.Tuple")) { + if (!tupleSymbol.fullName.startsWith("scala.Tuple") && caseClassUtil.fields.nonEmpty) { c.abort(c.enclosingPosition, s"Expected source type to be a tuple, but got: $tupleType") } diff --git a/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala b/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala index 8fc8715058..812ccf8a78 100644 --- a/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala +++ b/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala @@ -132,6 +132,9 @@ final case class TapirRequestTest13(@fileBody file: TapirFile) final case class TapirRequestTest14(@multipartBody form: Form) +@endpointInput("some/path") +final case class TapirRequestTest15() + final case class TapirResponseTest1( @header header1: Int, @@ -271,6 +274,12 @@ class DeriveEndpointIOTest extends AnyFlatSpec with Matchers with TableDrivenPro } } + it should "accept empty case classes when annotated with @endpointInput" in { + val expectedInput = stringToPath("some/path").mapTo[TapirRequestTest15] + val derived = EndpointInput.derived[TapirRequestTest15].asInstanceOf[EndpointInput.FixedPath[TapirRequestTest15]] + compareTransputs(EndpointInput.derived[TapirRequestTest15], expectedInput) shouldBe true + } + it should "not compile if there is field without annotation" in { assertDoesNotCompile(""" final case class Test( From 46e86237e0ba3e8ec772343e2cffcecc9f0d02c5 Mon Sep 17 00:00:00 2001 From: Boris Smidt Date: Sun, 16 Jan 2022 17:54:31 +0100 Subject: [PATCH 002/120] #1737 Using if else for emptyCaseclass instead of return --- .../EndpointInputAnnotationsMacro.scala | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointInputAnnotationsMacro.scala b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointInputAnnotationsMacro.scala index ab678d80e5..a3e5245c6b 100644 --- a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointInputAnnotationsMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointInputAnnotationsMacro.scala @@ -24,17 +24,26 @@ class EndpointInputAnnotationsMacro(override val c: blackbox.Context) extends En validateCaseClass(util) if (util.fields.isEmpty) { - val path = util.classSymbol.annotations - .map(_.tree) - .collectFirst { case Apply(Select(New(tree), _), List(arg)) if tree.tpe <:< endpointInput => arg } - - if (path.isEmpty) { - c.abort(c.enclosingPosition, s"case class is empty and not annotated with @endpointInput") - } else if (path.exists(tree => tree.toString().contains('{') || tree.toString().contains('}'))) { - c.abort(c.enclosingPosition, s"case class is empty but has path arguments in @endpointInput ${path.get}") - } - return c.Expr[EndpointInput[A]](q"stringToPath(${path.get}).mapTo[${util.classSymbol}]") + forEmptyCaseClass(util) + } else { + forCaseClass(util) + } + } + + private def forEmptyCaseClass[A: c.WeakTypeTag](util: CaseClassUtil[c.type,A]) = { + val path = util.classSymbol.annotations + .map(_.tree) + .collectFirst { case Apply(Select(New(tree), _), List(arg)) if tree.tpe <:< endpointInput => arg } + + if (path.isEmpty) { + c.abort(c.enclosingPosition, s"case class is empty and not annotated with @endpointInput") + } else if (path.exists(tree => tree.toString().contains('{') || tree.toString().contains('}'))) { + c.abort(c.enclosingPosition, s"case class is empty but has path arguments in @endpointInput ${path.get}") } + c.Expr[EndpointInput[A]](q"stringToPath(${path.get}).mapTo[${util.classSymbol}]") + } + + private def forCaseClass[A: c.WeakTypeTag](util: CaseClassUtil[c.type,A]) = { val segments = util.classSymbol.annotations .map(_.tree) .collectFirst { case Apply(Select(New(tree), _), List(arg)) if tree.tpe <:< endpointInput => arg } From cc899eb351cdea5505db6c9baf15b440c19cb4a5 Mon Sep 17 00:00:00 2001 From: Boris Smidt Date: Sun, 16 Jan 2022 19:07:49 +0100 Subject: [PATCH 003/120] #1737 I cannot use the mapTo in macro --- .../sttp/tapir/internal/AnnotationsMacros.scala | 15 +++++++++++++++ .../sttp/tapir/internal/MappingMacros.scala | 8 ++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala index 5072e68c4c..357a0a65ed 100644 --- a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala +++ b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala @@ -17,6 +17,21 @@ class AnnotationsMacros[T <: Product: Type](using q: Quotes) { private val caseClass = new CaseClass[q.type, T](using summon[Type[T]], q) def deriveEndpointInputImpl: Expr[EndpointInput[T]] = { + if (caseClass.fields.isEmpty) { + val path = caseClass + .extractOptStringArgFromAnnotation(endpointInputAnnotationSymbol) + .flatten + + if (path.exists(path => path.contains('{') && path.contains('}'))) { + report.throwError(s"${caseClass.name} is empty but @endpointInput contains a path variable") + } else { + '{ + stringToPath(${ + Expr(path.headOption.getOrElse("")) + }).mapTo[T] + } + } + } // the path inputs must be defined in the order as they appear in the argument to @endpointInput val pathSegments = caseClass .extractOptStringArgFromAnnotation(endpointInputAnnotationSymbol) diff --git a/core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala b/core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala index f3783d20d8..21e02f0689 100644 --- a/core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala +++ b/core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala @@ -10,10 +10,12 @@ object MappingMacros { checkFields[Out, In] val to: In => Out = { - case t: Tuple => mc.fromProduct(t) - case t => mc.fromProduct(Tuple1(t)) + case t: Tuple => mc.fromProduct(t) + case EmptyTuple => mc.fromProduct(EmptyTuple) + case t => mc.fromProduct(Tuple1(t)) } def from(out: Out): In = Tuple.fromProduct(out) match { + case EmptyTuple => EmptyTuple.asInstanceOf[In] case Tuple1(value) => value.asInstanceOf[In] case value => value.asInstanceOf[In] } @@ -22,8 +24,10 @@ object MappingMacros { inline def checkFields[A, B](using m: Mirror.ProductOf[A]): Unit = inline (erasedValue[m.MirroredElemTypes], erasedValue[B]) match { + case _: (EmptyTuple, B) => () case _: (B *: EmptyTuple, B) => () case _: (B, B) => () + case _: EmptyTuple => () case e => ComplietimeErrors.reportIncorrectMapping[B, A] } } From 8e966a61d0db1aa9c95ba5e653a31c2c76e13722 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Thu, 17 Nov 2022 12:18:15 +0100 Subject: [PATCH 004/120] Added ZIO based server for netty --- build.sbt | 15 +++ .../server/netty/zio/NettyZioServer.scala | 103 ++++++++++++++++++ .../netty/zio/NettyZioServerInterpreter.scala | 29 +++++ .../netty/zio/NettyZioServerOptions.scala | 61 +++++++++++ .../sttp/tapir/server/netty/zio/ZioUtil.scala | 13 +++ .../netty/zio/NettyZioServerStubTest.scala | 22 ++++ .../server/netty/zio/NettyZioServerTest.scala | 36 ++++++ .../zio/NettyZioTestServerInterpreter.scala | 40 +++++++ 8 files changed, 319 insertions(+) create mode 100644 server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala create mode 100644 server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala create mode 100644 server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala create mode 100644 server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala create mode 100644 server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerStubTest.scala create mode 100644 server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala create mode 100644 server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioTestServerInterpreter.scala diff --git a/build.sbt b/build.sbt index 1c873bd83e..1d90608bf8 100644 --- a/build.sbt +++ b/build.sbt @@ -179,6 +179,7 @@ lazy val rawAllAggregates = core.projectRefs ++ vertxServerZio1.projectRefs ++ nettyServer.projectRefs ++ nettyServerCats.projectRefs ++ + nettyServerZio.projectRefs++ zio1HttpServer.projectRefs ++ zioHttpServer.projectRefs ++ awsLambda.projectRefs ++ @@ -1274,6 +1275,20 @@ lazy val nettyServerCats: ProjectMatrix = (projectMatrix in file("server/netty-s .jvmPlatform(scalaVersions = scala2And3Versions) .dependsOn(serverCore, nettyServer, cats, serverTests % Test) +lazy val nettyServerZio: ProjectMatrix = (projectMatrix in file("server/netty-server/zio")) + .settings(commonJvmSettings) + .settings( + name := "tapir-netty-server-zio", + libraryDependencies ++= Seq( + "io.netty" % "netty-all" % "4.1.82.Final", + "dev.zio" %% "zio-interop-cats" % Versions.zioInteropCats, + ) ++ loggerDependencies, + // needed because of https://github.com/coursier/coursier/issues/2016 + useCoursier := false + ) + .jvmPlatform(scalaVersions = scala2And3Versions) + .dependsOn(nettyServer, zio, serverTests % Test) + lazy val vertxServer: ProjectMatrix = (projectMatrix in file("server/vertx-server")) .settings(commonJvmSettings) .settings( diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala new file mode 100644 index 0000000000..06f0225c9a --- /dev/null +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala @@ -0,0 +1,103 @@ +package sttp.tapir.server.netty.zio + +import io.netty.channel._ +import io.netty.channel.unix.DomainSocketAddress +import sttp.tapir.server.ServerEndpoint +import sttp.tapir.server.netty.Route +import sttp.tapir.server.netty.internal.{NettyBootstrap, NettyServerHandler} +import sttp.tapir.server.netty.zio.ZioUtil.{nettyChannelFutureToScala, nettyFutureToScala} +import sttp.tapir.ztapir.RIOMonadError +import zio.{RIO, Runtime, Unsafe, ZIO} + +import java.net.{InetSocketAddress, SocketAddress} +import java.nio.file.Path + +case class NettyZioServer[R, SA <: SocketAddress](routes: Vector[Route[RIO[R, *]]], options: NettyZioServerOptions[R, SA])(implicit + runtime: Runtime[R] +) { + def addEndpoint(se: ServerEndpoint[Any, RIO[R, *]]): NettyZioServer[R, SA] = addEndpoints(List(se)) + def addEndpoint( + se: ServerEndpoint[Any, RIO[R, *]], + overrideOptions: NettyZioServerOptions[R, SA] + ): NettyZioServer[R, SA] = + addEndpoints(List(se), overrideOptions) + def addEndpoints(ses: List[ServerEndpoint[Any, RIO[R, *]]]): NettyZioServer[R, SA] = addRoute( + NettyZioServerInterpreter[R](options).toRoute(ses) + ) + def addEndpoints( + ses: List[ServerEndpoint[Any, RIO[R, *]]], + overrideOptions: NettyZioServerOptions[R, SA] + ): NettyZioServer[R, SA] = addRoute( + NettyZioServerInterpreter[R](overrideOptions).toRoute(ses) + ) + + def addRoute(r: Route[RIO[R, *]]): NettyZioServer[R, SA] = copy(routes = routes :+ r) + def addRoutes(r: Iterable[Route[RIO[R, *]]]): NettyZioServer[R, SA] = copy(routes = routes ++ r) + + def options[SA2 <: SocketAddress](o: NettyZioServerOptions[R, SA2]): NettyZioServer[R, SA2] = copy(options = o) + + def host(hostname: String)(implicit isTCP: SA =:= InetSocketAddress): NettyZioServer[R, InetSocketAddress] = { + val nettyOptions = options.nettyOptions.host(hostname) + options(options.nettyOptions(nettyOptions)) + } + + def port(p: Int)(implicit isTCP: SA =:= InetSocketAddress): NettyZioServer[R, InetSocketAddress] = { + val nettyOptions = options.nettyOptions.port(p) + options(options.nettyOptions(nettyOptions)) + } + + def domainSocketPath(path: Path)(implicit isDomainSocket: SA =:= DomainSocketAddress): NettyZioServer[R, DomainSocketAddress] = { + val nettyOptions = options.nettyOptions.domainSocketPath(path) + options(options.nettyOptions(nettyOptions)) + } + + def start(): RIO[R, NettyZioServerBinding[R, SA]] = ZIO.suspend { + val eventLoopGroup = options.nettyOptions.eventLoopConfig.initEventLoopGroup() + implicit val monadError: RIOMonadError[R] = new RIOMonadError[R] + val route: Route[RIO[R, *]] = Route.combine(routes) + + val channelFuture = NettyBootstrap[RIO[R, *]]( + options.nettyOptions, + new NettyServerHandler[RIO[R, *]]( + route, + (f: () => RIO[R, Unit]) => Unsafe.unsafe(implicit u => runtime.unsafe.runToFuture(f())) + ), + eventLoopGroup + ) + + nettyChannelFutureToScala(channelFuture).map(ch => + NettyZioServerBinding( + ch.localAddress().asInstanceOf[SA], + () => stop(ch, eventLoopGroup) + ) + ) + } + + private def stop(ch: Channel, eventLoopGroup: EventLoopGroup): RIO[R, Unit] = { + ZIO.suspend { + nettyFutureToScala(ch.close()).flatMap { _ => + if (options.nettyOptions.shutdownEventLoopGroupOnClose) { + nettyFutureToScala(eventLoopGroup.shutdownGracefully()).map(_ => ()) + } else ZIO.succeed(()) + } + } + } +} + +object NettyZioServer { + def apply[R](implicit + runtime: Runtime[R] + ): NettyZioServer[R, InetSocketAddress] = + apply(NettyZioServerOptions.default[R]) + + def apply[R, SA <: SocketAddress](options: NettyZioServerOptions[R, SA])(implicit + runtime: Runtime[R] + ): NettyZioServer[R, SA] = + NettyZioServer(Vector.empty, options) +} + +case class NettyZioServerBinding[R, SA <: SocketAddress](localSocket: SA, stop: () => RIO[R, Unit]) { + def hostName(implicit isTCP: SA =:= InetSocketAddress): String = isTCP(localSocket).getHostName + def port(implicit isTCP: SA =:= InetSocketAddress): Int = isTCP(localSocket).getPort + def path(implicit isDomainSocket: SA =:= DomainSocketAddress): String = isDomainSocket(localSocket).path() +} diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala new file mode 100644 index 0000000000..43f39cfb75 --- /dev/null +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala @@ -0,0 +1,29 @@ +package sttp.tapir.server.netty.zio + +import sttp.tapir.server.ServerEndpoint +import sttp.tapir.server.netty.Route +import sttp.tapir.server.netty.internal.NettyServerInterpreter +import sttp.tapir.ztapir.RIOMonadError +import zio.RIO + +trait NettyZioServerInterpreter[R] { + def nettyServerOptions: NettyZioServerOptions[R, _] + + def toRoute(ses: List[ServerEndpoint[Any, RIO[R, *]]]): Route[RIO[R, *]] = { + implicit val monadError: RIOMonadError[R] = new RIOMonadError[R] + NettyServerInterpreter.toRoute(ses, nettyServerOptions.interceptors, nettyServerOptions.createFile, nettyServerOptions.deleteFile) + } +} + +object NettyZioServerInterpreter { + def apply[R]: NettyZioServerInterpreter[R] = { + new NettyZioServerInterpreter[R] { + override def nettyServerOptions: NettyZioServerOptions[R, _] = NettyZioServerOptions.default + } + } + def apply[R](options: NettyZioServerOptions[R, _]): NettyZioServerInterpreter[R] = { + new NettyZioServerInterpreter[R] { + override def nettyServerOptions: NettyZioServerOptions[R, _] = options + } + } +} diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala new file mode 100644 index 0000000000..29c3822851 --- /dev/null +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala @@ -0,0 +1,61 @@ +package sttp.tapir.server.netty.zio + +import com.typesafe.scalalogging.Logger +import sttp.tapir.model.ServerRequest +import sttp.tapir.server.interceptor.log.DefaultServerLog +import sttp.tapir.server.interceptor.{CustomiseInterceptors, Interceptor} +import sttp.tapir.server.netty.{NettyDefaults, NettyOptions} +import sttp.tapir.{Defaults, TapirFile} +import zio.{RIO, ZIO} + +import java.net.{InetSocketAddress, SocketAddress} + +case class NettyZioServerOptions[R, SA <: SocketAddress]( + interceptors: List[Interceptor[RIO[R, *]]], + createFile: ServerRequest => RIO[R, TapirFile], + deleteFile: TapirFile => RIO[R, Unit], + nettyOptions: NettyOptions[SA] +) { + def prependInterceptor(i: Interceptor[RIO[R, *]]): NettyZioServerOptions[R, SA] = copy(interceptors = i :: interceptors) + def appendInterceptor(i: Interceptor[RIO[R, *]]): NettyZioServerOptions[R, SA] = copy(interceptors = interceptors :+ i) + def nettyOptions[SA2 <: SocketAddress](o: NettyOptions[SA2]): NettyZioServerOptions[R, SA2] = copy(nettyOptions = o) +} + +object NettyZioServerOptions { + + /** Default options, using TCP sockets (the most common case). This can be later customised using + * [[NettyZioServerOptions#nettyOptions()]]. + */ + def default[R]: NettyZioServerOptions[R, InetSocketAddress] = + customiseInterceptors.options + + private def default[R]( + interceptors: List[Interceptor[RIO[R, *]]] + ): NettyZioServerOptions[R, InetSocketAddress] = + NettyZioServerOptions( + interceptors, + _ => ZIO.attemptBlocking(Defaults.createTempFile()), + file => ZIO.attemptBlocking(Defaults.deleteFile()(file)), + NettyOptions.default + ) + + def customiseInterceptors[R]: CustomiseInterceptors[RIO[R, *], NettyZioServerOptions[R, InetSocketAddress]] = + CustomiseInterceptors( + createOptions = (ci: CustomiseInterceptors[RIO[R, *], NettyZioServerOptions[R, InetSocketAddress]]) => default(ci.interceptors) + ).serverLog(defaultServerLog[R]) + + private val log: Logger = Logger[NettyZioServerInterpreter[Any]] + + def defaultServerLog[R]: DefaultServerLog[RIO[R, *]] = DefaultServerLog( + doLogWhenReceived = debugLog(_, None), + doLogWhenHandled = debugLog[R], + doLogAllDecodeFailures = debugLog[R], + doLogExceptions = (msg: String, ex: Throwable) => ZIO.succeed { log.warn(msg, ex) }, + noLog = ZIO.unit + ) + + private def debugLog[R](msg: String, exOpt: Option[Throwable]): RIO[R, Unit] = + ZIO.succeed(NettyDefaults.debugLog(log, msg, exOpt)) + + +} diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala new file mode 100644 index 0000000000..b69f01a9b8 --- /dev/null +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala @@ -0,0 +1,13 @@ +package sttp.tapir.server.netty.zio + +import io.netty.channel.{Channel, ChannelFuture} +import sttp.tapir.server.netty.internal.FutureUtil +import zio.{RIO, ZIO} + +object ZioUtil { + def nettyChannelFutureToScala[R](nettyFuture: ChannelFuture): RIO[R, Channel] = + ZIO.fromFuture(_ => FutureUtil.nettyChannelFutureToScala(nettyFuture)) + + def nettyFutureToScala[R, T](f: io.netty.util.concurrent.Future[T]): RIO[R, T] = + ZIO.fromFuture(_ => FutureUtil.nettyFutureToScala(f)) +} diff --git a/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerStubTest.scala b/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerStubTest.scala new file mode 100644 index 0000000000..b6f024f796 --- /dev/null +++ b/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerStubTest.scala @@ -0,0 +1,22 @@ +package sttp.tapir.server.netty.zio + +import sttp.client3.testing.SttpBackendStub +import sttp.tapir.server.interceptor.CustomiseInterceptors +import sttp.tapir.server.tests.{CreateServerStubTest, ServerStubTest} +import sttp.tapir.ztapir.RIOMonadError +import zio.{Runtime, Task, Unsafe} + +import java.net.InetSocketAddress +import scala.concurrent.Future + +class NettyZioCreateServerStubTest extends CreateServerStubTest[Task, NettyZioServerOptions[Any, InetSocketAddress]] { + + override def customiseInterceptors: CustomiseInterceptors[Task, NettyZioServerOptions[Any, InetSocketAddress]] = + NettyZioServerOptions.customiseInterceptors + override def stub[R]: SttpBackendStub[Task, R] = SttpBackendStub(new RIOMonadError[Any]()) + + override def asFuture[A]: Task[A] => Future[A] = task => Unsafe.unsafe(implicit u => Runtime.default.unsafe.runToFuture(task)) + +} + +class NettyZioServerStubTest extends ServerStubTest(new NettyZioCreateServerStubTest) diff --git a/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala b/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala new file mode 100644 index 0000000000..5eac6e8e10 --- /dev/null +++ b/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala @@ -0,0 +1,36 @@ +package sttp.tapir.server.netty.zio + +import cats.effect.{IO, Resource} +import io.netty.channel.nio.NioEventLoopGroup +import org.scalatest.EitherValues +import sttp.monad.MonadError +import sttp.tapir.server.netty.internal.FutureUtil +import sttp.tapir.server.tests._ +import sttp.tapir.tests.{Test, TestSuite} +import sttp.tapir.ztapir.RIOMonadError +import zio.Task + +class NettyZioServerTest extends TestSuite with EitherValues { + override def tests: Resource[IO, List[Test]] = + backendResource.flatMap { backend => + Resource + .make { + implicit val monadError: MonadError[Task] = new RIOMonadError[Any] + val eventLoopGroup = new NioEventLoopGroup() + + val interpreter = new NettyZioTestServerInterpreter(eventLoopGroup) + val createServerTest = new DefaultCreateServerTest(backend, interpreter) + + val tests = + new AllServerTests(createServerTest, interpreter, backend, staticContent = false, multipart = false, basic = false).tests() ++ + // I was unable to came up with why and how this tests fails - probably error cause whole interpreter to crush without sending 500 + new ServerBasicTests(createServerTest, interpreter, invulnerableToUnsanitizedHeaders = false).tests() ++ + new ServerMultipartTests(createServerTest, partOtherHeaderSupport = false).tests() + + IO.pure((tests, eventLoopGroup)) + } { case (_, eventLoopGroup) => + IO.fromFuture(IO.delay(FutureUtil.nettyFutureToScala(eventLoopGroup.shutdownGracefully()))).void + } + .map { case (tests, _) => tests } + } +} diff --git a/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioTestServerInterpreter.scala b/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioTestServerInterpreter.scala new file mode 100644 index 0000000000..1aae4bc148 --- /dev/null +++ b/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioTestServerInterpreter.scala @@ -0,0 +1,40 @@ +package sttp.tapir.server.netty.zio + +import cats.data.NonEmptyList +import cats.effect.{IO, Resource} +import io.netty.channel.nio.NioEventLoopGroup +import sttp.tapir.server.ServerEndpoint +import sttp.tapir.server.netty.{NettyOptions, Route} +import sttp.tapir.server.tests.TestServerInterpreter +import sttp.tapir.tests.Port +import zio.{CancelableFuture, Runtime, Task, Unsafe} + +import java.net.InetSocketAddress + +class NettyZioTestServerInterpreter[R](eventLoopGroup: NioEventLoopGroup) + extends TestServerInterpreter[Task, Any, NettyZioServerOptions[Any, InetSocketAddress], Route[Task]] { + implicit val runtime: Runtime[R] = Runtime.default.asInstanceOf[Runtime[R]] + + override def route(es: List[ServerEndpoint[Any, Task]], interceptors: Interceptors): Route[Task] = { + val serverOptions: NettyZioServerOptions[Any, InetSocketAddress] = interceptors( + NettyZioServerOptions.customiseInterceptors + ).options + NettyZioServerInterpreter(serverOptions).toRoute(es) + } + + override def server(routes: NonEmptyList[Route[Task]]): Resource[IO, Port] = { + val options = + NettyZioServerOptions + .default[R] + .nettyOptions(NettyOptions.default.eventLoopGroup(eventLoopGroup).randomPort.noShutdownOnClose) + + val server: CancelableFuture[NettyZioServerBinding[R, InetSocketAddress]] = + Unsafe.unsafe(implicit u => runtime.unsafe.runToFuture(NettyZioServer(options).addRoutes(routes.toList).start())) + + Resource + .make(IO.fromFuture(IO(server)))(binding => + IO.fromFuture(IO(Unsafe.unsafe(implicit u => runtime.unsafe.runToFuture(binding.stop())))) + ) + .map(b => b.port) + } +} From 1aef4d743170564cd72e16f2dbccbc292b4cfcf2 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Thu, 17 Nov 2022 13:42:03 +0100 Subject: [PATCH 005/120] Added zio1 based netty server --- build.sbt | 15 +++ .../server/netty/zio/NettyZioServer.scala | 103 ++++++++++++++++++ .../netty/zio/NettyZioServerInterpreter.scala | 29 +++++ .../netty/zio/NettyZioServerOptions.scala | 61 +++++++++++ .../sttp/tapir/server/netty/zio/ZioUtil.scala | 13 +++ .../netty/zio/NettyZioServerStubTest.scala | 22 ++++ .../server/netty/zio/NettyZioServerTest.scala | 36 ++++++ .../zio/NettyZioTestServerInterpreter.scala | 38 +++++++ 8 files changed, 317 insertions(+) create mode 100644 server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala create mode 100644 server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala create mode 100644 server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala create mode 100644 server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala create mode 100644 server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerStubTest.scala create mode 100644 server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala create mode 100644 server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioTestServerInterpreter.scala diff --git a/build.sbt b/build.sbt index 1d90608bf8..ab1a3ec75b 100644 --- a/build.sbt +++ b/build.sbt @@ -180,6 +180,7 @@ lazy val rawAllAggregates = core.projectRefs ++ nettyServer.projectRefs ++ nettyServerCats.projectRefs ++ nettyServerZio.projectRefs++ + nettyServerZio1.projectRefs ++ zio1HttpServer.projectRefs ++ zioHttpServer.projectRefs ++ awsLambda.projectRefs ++ @@ -1289,6 +1290,20 @@ lazy val nettyServerZio: ProjectMatrix = (projectMatrix in file("server/netty-se .jvmPlatform(scalaVersions = scala2And3Versions) .dependsOn(nettyServer, zio, serverTests % Test) +lazy val nettyServerZio1: ProjectMatrix = (projectMatrix in file("server/netty-server/zio1")) + .settings(commonJvmSettings) + .settings( + name := "tapir-netty-server-zio1", + libraryDependencies ++= Seq( + "io.netty" % "netty-all" % "4.1.82.Final", + "dev.zio" %% "zio-interop-cats" % Versions.zio1InteropCats, + ) ++ loggerDependencies, + // needed because of https://github.com/coursier/coursier/issues/2016 + useCoursier := false + ) + .jvmPlatform(scalaVersions = scala2And3Versions) + .dependsOn(nettyServer, zio1, serverTests % Test) + lazy val vertxServer: ProjectMatrix = (projectMatrix in file("server/vertx-server")) .settings(commonJvmSettings) .settings( diff --git a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala new file mode 100644 index 0000000000..ecf61b0146 --- /dev/null +++ b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala @@ -0,0 +1,103 @@ +package sttp.tapir.server.netty.zio + +import io.netty.channel._ +import io.netty.channel.unix.DomainSocketAddress +import sttp.tapir.server.ServerEndpoint +import sttp.tapir.server.netty.Route +import sttp.tapir.server.netty.internal.{NettyBootstrap, NettyServerHandler} +import sttp.tapir.server.netty.zio.ZioUtil.{nettyChannelFutureToScala, nettyFutureToScala} +import sttp.tapir.ztapir.RIOMonadError +import zio.{RIO, Runtime, ZIO} + +import java.net.{InetSocketAddress, SocketAddress} +import java.nio.file.Path + +case class NettyZioServer[R, SA <: SocketAddress](routes: Vector[Route[RIO[R, *]]], options: NettyZioServerOptions[R, SA])(implicit + runtime: Runtime[R] +) { + def addEndpoint(se: ServerEndpoint[Any, RIO[R, *]]): NettyZioServer[R, SA] = addEndpoints(List(se)) + def addEndpoint( + se: ServerEndpoint[Any, RIO[R, *]], + overrideOptions: NettyZioServerOptions[R, SA] + ): NettyZioServer[R, SA] = + addEndpoints(List(se), overrideOptions) + def addEndpoints(ses: List[ServerEndpoint[Any, RIO[R, *]]]): NettyZioServer[R, SA] = addRoute( + NettyZioServerInterpreter[R](options).toRoute(ses) + ) + def addEndpoints( + ses: List[ServerEndpoint[Any, RIO[R, *]]], + overrideOptions: NettyZioServerOptions[R, SA] + ): NettyZioServer[R, SA] = addRoute( + NettyZioServerInterpreter[R](overrideOptions).toRoute(ses) + ) + + def addRoute(r: Route[RIO[R, *]]): NettyZioServer[R, SA] = copy(routes = routes :+ r) + def addRoutes(r: Iterable[Route[RIO[R, *]]]): NettyZioServer[R, SA] = copy(routes = routes ++ r) + + def options[SA2 <: SocketAddress](o: NettyZioServerOptions[R, SA2]): NettyZioServer[R, SA2] = copy(options = o) + + def host(hostname: String)(implicit isTCP: SA =:= InetSocketAddress): NettyZioServer[R, InetSocketAddress] = { + val nettyOptions = options.nettyOptions.host(hostname) + options(options.nettyOptions(nettyOptions)) + } + + def port(p: Int)(implicit isTCP: SA =:= InetSocketAddress): NettyZioServer[R, InetSocketAddress] = { + val nettyOptions = options.nettyOptions.port(p) + options(options.nettyOptions(nettyOptions)) + } + + def domainSocketPath(path: Path)(implicit isDomainSocket: SA =:= DomainSocketAddress): NettyZioServer[R, DomainSocketAddress] = { + val nettyOptions = options.nettyOptions.domainSocketPath(path) + options(options.nettyOptions(nettyOptions)) + } + + def start(): RIO[R, NettyZioServerBinding[R, SA]] = ZIO.effectSuspend { + val eventLoopGroup = options.nettyOptions.eventLoopConfig.initEventLoopGroup() + implicit val monadError: RIOMonadError[R] = new RIOMonadError[R] + val route: Route[RIO[R, *]] = Route.combine(routes) + + val channelFuture = NettyBootstrap[RIO[R, *]]( + options.nettyOptions, + new NettyServerHandler[RIO[R, *]]( + route, + (f: () => RIO[R, Unit]) => runtime.unsafeRunTask(f()) + ), + eventLoopGroup + ) + + nettyChannelFutureToScala(channelFuture).map(ch => + NettyZioServerBinding( + ch.localAddress().asInstanceOf[SA], + () => stop(ch, eventLoopGroup) + ) + ) + } + + private def stop(ch: Channel, eventLoopGroup: EventLoopGroup): RIO[R, Unit] = { + ZIO.effectSuspend { + nettyFutureToScala(ch.close()).flatMap { _ => + if (options.nettyOptions.shutdownEventLoopGroupOnClose) { + nettyFutureToScala(eventLoopGroup.shutdownGracefully()).map(_ => ()) + } else ZIO.succeed(()) + } + } + } +} + +object NettyZioServer { + def apply[R](implicit + runtime: Runtime[R] + ): NettyZioServer[R, InetSocketAddress] = + apply(NettyZioServerOptions.default[R]) + + def apply[R, SA <: SocketAddress](options: NettyZioServerOptions[R, SA])(implicit + runtime: Runtime[R] + ): NettyZioServer[R, SA] = + NettyZioServer(Vector.empty, options) +} + +case class NettyZioServerBinding[R, SA <: SocketAddress](localSocket: SA, stop: () => RIO[R, Unit]) { + def hostName(implicit isTCP: SA =:= InetSocketAddress): String = isTCP(localSocket).getHostName + def port(implicit isTCP: SA =:= InetSocketAddress): Int = isTCP(localSocket).getPort + def path(implicit isDomainSocket: SA =:= DomainSocketAddress): String = isDomainSocket(localSocket).path() +} diff --git a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala new file mode 100644 index 0000000000..43f39cfb75 --- /dev/null +++ b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala @@ -0,0 +1,29 @@ +package sttp.tapir.server.netty.zio + +import sttp.tapir.server.ServerEndpoint +import sttp.tapir.server.netty.Route +import sttp.tapir.server.netty.internal.NettyServerInterpreter +import sttp.tapir.ztapir.RIOMonadError +import zio.RIO + +trait NettyZioServerInterpreter[R] { + def nettyServerOptions: NettyZioServerOptions[R, _] + + def toRoute(ses: List[ServerEndpoint[Any, RIO[R, *]]]): Route[RIO[R, *]] = { + implicit val monadError: RIOMonadError[R] = new RIOMonadError[R] + NettyServerInterpreter.toRoute(ses, nettyServerOptions.interceptors, nettyServerOptions.createFile, nettyServerOptions.deleteFile) + } +} + +object NettyZioServerInterpreter { + def apply[R]: NettyZioServerInterpreter[R] = { + new NettyZioServerInterpreter[R] { + override def nettyServerOptions: NettyZioServerOptions[R, _] = NettyZioServerOptions.default + } + } + def apply[R](options: NettyZioServerOptions[R, _]): NettyZioServerInterpreter[R] = { + new NettyZioServerInterpreter[R] { + override def nettyServerOptions: NettyZioServerOptions[R, _] = options + } + } +} diff --git a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala new file mode 100644 index 0000000000..c27185d13c --- /dev/null +++ b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala @@ -0,0 +1,61 @@ +package sttp.tapir.server.netty.zio + +import com.typesafe.scalalogging.Logger +import sttp.tapir.model.ServerRequest +import sttp.tapir.server.interceptor.log.DefaultServerLog +import sttp.tapir.server.interceptor.{CustomiseInterceptors, Interceptor} +import sttp.tapir.server.netty.{NettyDefaults, NettyOptions} +import sttp.tapir.{Defaults, TapirFile} +import zio.{RIO, ZIO} + +import java.net.{InetSocketAddress, SocketAddress} + +case class NettyZioServerOptions[R, SA <: SocketAddress]( + interceptors: List[Interceptor[RIO[R, *]]], + createFile: ServerRequest => RIO[R, TapirFile], + deleteFile: TapirFile => RIO[R, Unit], + nettyOptions: NettyOptions[SA] +) { + def prependInterceptor(i: Interceptor[RIO[R, *]]): NettyZioServerOptions[R, SA] = copy(interceptors = i :: interceptors) + def appendInterceptor(i: Interceptor[RIO[R, *]]): NettyZioServerOptions[R, SA] = copy(interceptors = interceptors :+ i) + def nettyOptions[SA2 <: SocketAddress](o: NettyOptions[SA2]): NettyZioServerOptions[R, SA2] = copy(nettyOptions = o) +} + +object NettyZioServerOptions { + + /** Default options, using TCP sockets (the most common case). This can be later customised using + * [[NettyZioServerOptions#nettyOptions()]]. + */ + def default[R]: NettyZioServerOptions[R, InetSocketAddress] = + customiseInterceptors.options + + private def default[R]( + interceptors: List[Interceptor[RIO[R, *]]] + ): NettyZioServerOptions[R, InetSocketAddress] = + NettyZioServerOptions( + interceptors, + _ => ZIO(Defaults.createTempFile()), + file => ZIO(Defaults.deleteFile()(file)), + NettyOptions.default + ) + + def customiseInterceptors[R]: CustomiseInterceptors[RIO[R, *], NettyZioServerOptions[R, InetSocketAddress]] = + CustomiseInterceptors( + createOptions = (ci: CustomiseInterceptors[RIO[R, *], NettyZioServerOptions[R, InetSocketAddress]]) => default(ci.interceptors) + ).serverLog(defaultServerLog[R]) + + private val log: Logger = Logger[NettyZioServerInterpreter[Any]] + + def defaultServerLog[R]: DefaultServerLog[RIO[R, *]] = DefaultServerLog( + doLogWhenReceived = debugLog(_, None), + doLogWhenHandled = debugLog[R], + doLogAllDecodeFailures = debugLog[R], + doLogExceptions = (msg: String, ex: Throwable) => ZIO.succeed { log.warn(msg, ex) }, + noLog = ZIO.unit + ) + + private def debugLog[R](msg: String, exOpt: Option[Throwable]): RIO[R, Unit] = + ZIO.succeed(NettyDefaults.debugLog(log, msg, exOpt)) + + +} diff --git a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala new file mode 100644 index 0000000000..b69f01a9b8 --- /dev/null +++ b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala @@ -0,0 +1,13 @@ +package sttp.tapir.server.netty.zio + +import io.netty.channel.{Channel, ChannelFuture} +import sttp.tapir.server.netty.internal.FutureUtil +import zio.{RIO, ZIO} + +object ZioUtil { + def nettyChannelFutureToScala[R](nettyFuture: ChannelFuture): RIO[R, Channel] = + ZIO.fromFuture(_ => FutureUtil.nettyChannelFutureToScala(nettyFuture)) + + def nettyFutureToScala[R, T](f: io.netty.util.concurrent.Future[T]): RIO[R, T] = + ZIO.fromFuture(_ => FutureUtil.nettyFutureToScala(f)) +} diff --git a/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerStubTest.scala b/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerStubTest.scala new file mode 100644 index 0000000000..15be6abcba --- /dev/null +++ b/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerStubTest.scala @@ -0,0 +1,22 @@ +package sttp.tapir.server.netty.zio + +import sttp.client3.testing.SttpBackendStub +import sttp.tapir.server.interceptor.CustomiseInterceptors +import sttp.tapir.server.tests.{CreateServerStubTest, ServerStubTest} +import sttp.tapir.ztapir.RIOMonadError +import zio.Task + +import java.net.InetSocketAddress +import scala.concurrent.Future + +class NettyZioCreateServerStubTest extends CreateServerStubTest[Task, NettyZioServerOptions[Any, InetSocketAddress]] { + + override def customiseInterceptors: CustomiseInterceptors[Task, NettyZioServerOptions[Any, InetSocketAddress]] = + NettyZioServerOptions.customiseInterceptors + override def stub[R]: SttpBackendStub[Task, R] = SttpBackendStub(new RIOMonadError[Any]()) + + override def asFuture[A]: Task[A] => Future[A] = task => zio.Runtime.default.unsafeRunToFuture(task) + +} + +class NettyZioServerStubTest extends ServerStubTest(new NettyZioCreateServerStubTest) diff --git a/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala b/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala new file mode 100644 index 0000000000..5eac6e8e10 --- /dev/null +++ b/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala @@ -0,0 +1,36 @@ +package sttp.tapir.server.netty.zio + +import cats.effect.{IO, Resource} +import io.netty.channel.nio.NioEventLoopGroup +import org.scalatest.EitherValues +import sttp.monad.MonadError +import sttp.tapir.server.netty.internal.FutureUtil +import sttp.tapir.server.tests._ +import sttp.tapir.tests.{Test, TestSuite} +import sttp.tapir.ztapir.RIOMonadError +import zio.Task + +class NettyZioServerTest extends TestSuite with EitherValues { + override def tests: Resource[IO, List[Test]] = + backendResource.flatMap { backend => + Resource + .make { + implicit val monadError: MonadError[Task] = new RIOMonadError[Any] + val eventLoopGroup = new NioEventLoopGroup() + + val interpreter = new NettyZioTestServerInterpreter(eventLoopGroup) + val createServerTest = new DefaultCreateServerTest(backend, interpreter) + + val tests = + new AllServerTests(createServerTest, interpreter, backend, staticContent = false, multipart = false, basic = false).tests() ++ + // I was unable to came up with why and how this tests fails - probably error cause whole interpreter to crush without sending 500 + new ServerBasicTests(createServerTest, interpreter, invulnerableToUnsanitizedHeaders = false).tests() ++ + new ServerMultipartTests(createServerTest, partOtherHeaderSupport = false).tests() + + IO.pure((tests, eventLoopGroup)) + } { case (_, eventLoopGroup) => + IO.fromFuture(IO.delay(FutureUtil.nettyFutureToScala(eventLoopGroup.shutdownGracefully()))).void + } + .map { case (tests, _) => tests } + } +} diff --git a/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioTestServerInterpreter.scala b/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioTestServerInterpreter.scala new file mode 100644 index 0000000000..acc1ecc689 --- /dev/null +++ b/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioTestServerInterpreter.scala @@ -0,0 +1,38 @@ +package sttp.tapir.server.netty.zio + +import cats.data.NonEmptyList +import cats.effect.{IO, Resource} +import io.netty.channel.nio.NioEventLoopGroup +import sttp.tapir.server.ServerEndpoint +import sttp.tapir.server.netty.{NettyOptions, Route} +import sttp.tapir.server.tests.TestServerInterpreter +import sttp.tapir.tests.Port +import zio.{Runtime, Task} + +import java.net.InetSocketAddress + +class NettyZioTestServerInterpreter[R](eventLoopGroup: NioEventLoopGroup) + extends TestServerInterpreter[Task, Any, NettyZioServerOptions[Any, InetSocketAddress], Route[Task]] { + implicit val runtime: Runtime[R] = Runtime.default.asInstanceOf[Runtime[R]] + + override def route(es: List[ServerEndpoint[Any, Task]], interceptors: Interceptors): Route[Task] = { + val serverOptions: NettyZioServerOptions[Any, InetSocketAddress] = interceptors( + NettyZioServerOptions.customiseInterceptors + ).options + NettyZioServerInterpreter(serverOptions).toRoute(es) + } + + override def server(routes: NonEmptyList[Route[Task]]): Resource[IO, Port] = { + val options = + NettyZioServerOptions + .default[R] + .nettyOptions(NettyOptions.default.eventLoopGroup(eventLoopGroup).randomPort.noShutdownOnClose) + + val server: NettyZioServerBinding[R, InetSocketAddress] = + runtime.unsafeRun(NettyZioServer(options).addRoutes(routes.toList).start()) + + Resource + .make(IO(server))(binding => IO(runtime.unsafeRun(binding.stop()))) + .map(b => b.port) + } +} From 41cf9d02687e1ed84a7b96f88faddfaaf1fda922 Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 17 Nov 2022 16:24:02 +0100 Subject: [PATCH 006/120] Fix tests for ZIO2 --- .../netty/internal/NettyServerHandler.scala | 108 ++++++++++-------- .../netty/zio/NettyZioServerInterpreter.scala | 5 +- .../server/netty/zio/NettyZioServerTest.scala | 4 +- 3 files changed, 63 insertions(+), 54 deletions(-) diff --git a/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/NettyServerHandler.scala b/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/NettyServerHandler.scala index 5634928f55..7842f6b3c6 100644 --- a/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/NettyServerHandler.scala +++ b/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/NettyServerHandler.scala @@ -35,56 +35,14 @@ class NettyServerHandler[F[_]](route: Route[F], unsafeRunAsync: (() => F[Unit]) case Some(response) => response case None => ServerResponse.notFound } - .map((serverResponse: ServerResponse[NettyResponse]) => { - serverResponse.handle( - ctx = ctx, - byteBufHandler = (channelPromise, byteBuf) => { - val res = new DefaultFullHttpResponse(req.protocolVersion(), HttpResponseStatus.valueOf(serverResponse.code.code), byteBuf) - - res.setHeadersFrom(serverResponse) - res.handleContentLengthAndChunkedHeaders(Option(byteBuf.readableBytes())) - res.handleCloseAndKeepAliveHeaders(req) - - ctx.writeAndFlush(res, channelPromise).closeIfNeeded(req) - }, - chunkedStreamHandler = (channelPromise, chunkedStream) => { - val resHeader: DefaultHttpResponse = - new DefaultHttpResponse(req.protocolVersion(), HttpResponseStatus.valueOf(serverResponse.code.code)) - - resHeader.setHeadersFrom(serverResponse) - resHeader.handleContentLengthAndChunkedHeaders(None) - resHeader.handleCloseAndKeepAliveHeaders(req) - - ctx.write(resHeader) - ctx.writeAndFlush(new HttpChunkedInput(chunkedStream), channelPromise).closeIfNeeded(req) - }, - chunkedFileHandler = (channelPromise, chunkedFile) => { - val resHeader: DefaultHttpResponse = - new DefaultHttpResponse(req.protocolVersion(), HttpResponseStatus.valueOf(serverResponse.code.code)) - - resHeader.setHeadersFrom(serverResponse) - resHeader.handleContentLengthAndChunkedHeaders(Option(chunkedFile.length())) - resHeader.handleCloseAndKeepAliveHeaders(req) - - ctx.write(resHeader) - // HttpChunkedInput will write the end marker (LastHttpContent) for us. - ctx.writeAndFlush(new HttpChunkedInput(chunkedFile), channelPromise).closeIfNeeded(req) - }, - noBodyHandler = () => { - val res = new DefaultFullHttpResponse( - req.protocolVersion(), - HttpResponseStatus.valueOf(serverResponse.code.code), - Unpooled.EMPTY_BUFFER - ) - - res.setHeadersFrom(serverResponse) - res.handleContentLengthAndChunkedHeaders(Option(Unpooled.EMPTY_BUFFER.readableBytes())) - res.handleCloseAndKeepAliveHeaders(req) - - ctx.writeAndFlush(res).closeIfNeeded(req) - } - ) - }) + .flatMap((serverResponse: ServerResponse[NettyResponse]) => + // in ZIO, exceptions thrown in .map become defects - instead, we want them represented as errors so that + // we get the 500 response, instead of dropping the request + try handleResponse(ctx, req, serverResponse).unit + catch { + case e: Exception => me.error[Unit](e) + } + ) .handleError { case ex: Exception => logger.error("Error while processing the request", ex) // send 500 @@ -101,6 +59,56 @@ class NettyServerHandler[F[_]](route: Route[F], unsafeRunAsync: (() => F[Unit]) } } + private def handleResponse(ctx: ChannelHandlerContext, req: FullHttpRequest, serverResponse: ServerResponse[NettyResponse]): Unit = + serverResponse.handle( + ctx = ctx, + byteBufHandler = (channelPromise, byteBuf) => { + val res = new DefaultFullHttpResponse(req.protocolVersion(), HttpResponseStatus.valueOf(serverResponse.code.code), byteBuf) + + res.setHeadersFrom(serverResponse) + res.handleContentLengthAndChunkedHeaders(Option(byteBuf.readableBytes())) + res.handleCloseAndKeepAliveHeaders(req) + + ctx.writeAndFlush(res, channelPromise).closeIfNeeded(req) + }, + chunkedStreamHandler = (channelPromise, chunkedStream) => { + val resHeader: DefaultHttpResponse = + new DefaultHttpResponse(req.protocolVersion(), HttpResponseStatus.valueOf(serverResponse.code.code)) + + resHeader.setHeadersFrom(serverResponse) + resHeader.handleContentLengthAndChunkedHeaders(None) + resHeader.handleCloseAndKeepAliveHeaders(req) + + ctx.write(resHeader) + ctx.writeAndFlush(new HttpChunkedInput(chunkedStream), channelPromise).closeIfNeeded(req) + }, + chunkedFileHandler = (channelPromise, chunkedFile) => { + val resHeader: DefaultHttpResponse = + new DefaultHttpResponse(req.protocolVersion(), HttpResponseStatus.valueOf(serverResponse.code.code)) + + resHeader.setHeadersFrom(serverResponse) + resHeader.handleContentLengthAndChunkedHeaders(Option(chunkedFile.length())) + resHeader.handleCloseAndKeepAliveHeaders(req) + + ctx.write(resHeader) + // HttpChunkedInput will write the end marker (LastHttpContent) for us. + ctx.writeAndFlush(new HttpChunkedInput(chunkedFile), channelPromise).closeIfNeeded(req) + }, + noBodyHandler = () => { + val res = new DefaultFullHttpResponse( + req.protocolVersion(), + HttpResponseStatus.valueOf(serverResponse.code.code), + Unpooled.EMPTY_BUFFER + ) + + res.setHeadersFrom(serverResponse) + res.handleContentLengthAndChunkedHeaders(Option(Unpooled.EMPTY_BUFFER.readableBytes())) + res.handleCloseAndKeepAliveHeaders(req) + + ctx.writeAndFlush(res).closeIfNeeded(req) + } + ) + private implicit class RichServerNettyResponse(val r: ServerResponse[NettyResponse]) { def handle( ctx: ChannelHandlerContext, diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala index 43f39cfb75..07d60b2dfb 100644 --- a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala @@ -11,7 +11,10 @@ trait NettyZioServerInterpreter[R] { def toRoute(ses: List[ServerEndpoint[Any, RIO[R, *]]]): Route[RIO[R, *]] = { implicit val monadError: RIOMonadError[R] = new RIOMonadError[R] - NettyServerInterpreter.toRoute(ses, nettyServerOptions.interceptors, nettyServerOptions.createFile, nettyServerOptions.deleteFile) + NettyServerInterpreter + .toRoute(ses, nettyServerOptions.interceptors, nettyServerOptions.createFile, nettyServerOptions.deleteFile) + // we want to log & return a 500 in case of defects as well + .andThen(_.resurrect) } } diff --git a/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala b/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala index 5eac6e8e10..11fbe53830 100644 --- a/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala +++ b/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala @@ -22,9 +22,7 @@ class NettyZioServerTest extends TestSuite with EitherValues { val createServerTest = new DefaultCreateServerTest(backend, interpreter) val tests = - new AllServerTests(createServerTest, interpreter, backend, staticContent = false, multipart = false, basic = false).tests() ++ - // I was unable to came up with why and how this tests fails - probably error cause whole interpreter to crush without sending 500 - new ServerBasicTests(createServerTest, interpreter, invulnerableToUnsanitizedHeaders = false).tests() ++ + new AllServerTests(createServerTest, interpreter, backend, staticContent = false, multipart = false).tests() ++ new ServerMultipartTests(createServerTest, partOtherHeaderSupport = false).tests() IO.pure((tests, eventLoopGroup)) From 8434c62d00e01d68b31c1ea84dafc2be5f714e71 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Fri, 18 Nov 2022 09:20:32 +0100 Subject: [PATCH 007/120] Fix review issues --- build.sbt | 7 ++----- project/Versions.scala | 1 + .../tapir/server/netty/zio/NettyZioServer.scala | 10 +++++----- .../netty/zio/NettyZioServerInterpreter.scala | 5 ++--- .../server/netty/zio/{ => internal}/ZioUtil.scala | 4 ++-- .../tapir/server/netty/zio/NettyZioServer.scala | 13 ++++++------- .../netty/zio/NettyZioServerInterpreter.scala | 5 ++--- .../server/netty/zio/{ => internal}/ZioUtil.scala | 4 ++-- .../tapir/server/netty/zio/NettyZioServerTest.scala | 7 ++----- 9 files changed, 24 insertions(+), 32 deletions(-) rename server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/{ => internal}/ZioUtil.scala (85%) rename server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/{ => internal}/ZioUtil.scala (85%) diff --git a/build.sbt b/build.sbt index ab1a3ec75b..7099a524a1 100644 --- a/build.sbt +++ b/build.sbt @@ -1254,7 +1254,7 @@ lazy val nettyServer: ProjectMatrix = (projectMatrix in file("server/netty-serve .settings( name := "tapir-netty-server", libraryDependencies ++= Seq( - "io.netty" % "netty-all" % "4.1.82.Final" + "io.netty" % "netty-all" % Versions.nettyAll ) ++ loggerDependencies, // needed because of https://github.com/coursier/coursier/issues/2016 useCoursier := false @@ -1267,21 +1267,19 @@ lazy val nettyServerCats: ProjectMatrix = (projectMatrix in file("server/netty-s .settings( name := "tapir-netty-server-cats", libraryDependencies ++= Seq( - "io.netty" % "netty-all" % "4.1.82.Final", "com.softwaremill.sttp.shared" %% "fs2" % Versions.sttpShared ) ++ loggerDependencies, // needed because of https://github.com/coursier/coursier/issues/2016 useCoursier := false ) .jvmPlatform(scalaVersions = scala2And3Versions) - .dependsOn(serverCore, nettyServer, cats, serverTests % Test) + .dependsOn(nettyServer, cats, serverTests % Test) lazy val nettyServerZio: ProjectMatrix = (projectMatrix in file("server/netty-server/zio")) .settings(commonJvmSettings) .settings( name := "tapir-netty-server-zio", libraryDependencies ++= Seq( - "io.netty" % "netty-all" % "4.1.82.Final", "dev.zio" %% "zio-interop-cats" % Versions.zioInteropCats, ) ++ loggerDependencies, // needed because of https://github.com/coursier/coursier/issues/2016 @@ -1295,7 +1293,6 @@ lazy val nettyServerZio1: ProjectMatrix = (projectMatrix in file("server/netty-s .settings( name := "tapir-netty-server-zio1", libraryDependencies ++= Seq( - "io.netty" % "netty-all" % "4.1.82.Final", "dev.zio" %% "zio-interop-cats" % Versions.zio1InteropCats, ) ++ loggerDependencies, // needed because of https://github.com/coursier/coursier/issues/2016 diff --git a/project/Versions.scala b/project/Versions.scala index e9f4d4f009..8c5d85396a 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -53,4 +53,5 @@ object Versions { val openTelemetry = "1.20.1" val mockServer = "5.14.0" val dogstatsdClient = "4.1.0" + val nettyAll = "4.1.82.Final" } diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala index 06f0225c9a..eaa75d1f91 100644 --- a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala @@ -5,8 +5,8 @@ import io.netty.channel.unix.DomainSocketAddress import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.netty.Route import sttp.tapir.server.netty.internal.{NettyBootstrap, NettyServerHandler} -import sttp.tapir.server.netty.zio.ZioUtil.{nettyChannelFutureToScala, nettyFutureToScala} -import sttp.tapir.ztapir.RIOMonadError +import sttp.tapir.server.netty.zio.internal.ZioUtil.{nettyChannelFutureToScala, nettyFutureToScala} +import sttp.tapir.ztapir.{RIOMonadError, ZServerEndpoint} import zio.{RIO, Runtime, Unsafe, ZIO} import java.net.{InetSocketAddress, SocketAddress} @@ -15,9 +15,9 @@ import java.nio.file.Path case class NettyZioServer[R, SA <: SocketAddress](routes: Vector[Route[RIO[R, *]]], options: NettyZioServerOptions[R, SA])(implicit runtime: Runtime[R] ) { - def addEndpoint(se: ServerEndpoint[Any, RIO[R, *]]): NettyZioServer[R, SA] = addEndpoints(List(se)) + def addEndpoint(se: ZServerEndpoint[R, Any]): NettyZioServer[R, SA] = addEndpoints(List(se)) def addEndpoint( - se: ServerEndpoint[Any, RIO[R, *]], + se: ZServerEndpoint[R, Any], overrideOptions: NettyZioServerOptions[R, SA] ): NettyZioServer[R, SA] = addEndpoints(List(se), overrideOptions) @@ -25,7 +25,7 @@ case class NettyZioServer[R, SA <: SocketAddress](routes: Vector[Route[RIO[R, *] NettyZioServerInterpreter[R](options).toRoute(ses) ) def addEndpoints( - ses: List[ServerEndpoint[Any, RIO[R, *]]], + ses: List[ZServerEndpoint[R, Any]], overrideOptions: NettyZioServerOptions[R, SA] ): NettyZioServer[R, SA] = addRoute( NettyZioServerInterpreter[R](overrideOptions).toRoute(ses) diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala index 07d60b2dfb..435fc4084c 100644 --- a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala @@ -1,15 +1,14 @@ package sttp.tapir.server.netty.zio -import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.netty.Route import sttp.tapir.server.netty.internal.NettyServerInterpreter -import sttp.tapir.ztapir.RIOMonadError +import sttp.tapir.ztapir.{RIOMonadError, ZServerEndpoint} import zio.RIO trait NettyZioServerInterpreter[R] { def nettyServerOptions: NettyZioServerOptions[R, _] - def toRoute(ses: List[ServerEndpoint[Any, RIO[R, *]]]): Route[RIO[R, *]] = { + def toRoute(ses: List[ZServerEndpoint[R, Any]]): Route[RIO[R, *]] = { implicit val monadError: RIOMonadError[R] = new RIOMonadError[R] NettyServerInterpreter .toRoute(ses, nettyServerOptions.interceptors, nettyServerOptions.createFile, nettyServerOptions.deleteFile) diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/internal/ZioUtil.scala similarity index 85% rename from server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala rename to server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/internal/ZioUtil.scala index b69f01a9b8..e08cea1d84 100644 --- a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/internal/ZioUtil.scala @@ -1,10 +1,10 @@ -package sttp.tapir.server.netty.zio +package sttp.tapir.server.netty.zio.internal import io.netty.channel.{Channel, ChannelFuture} import sttp.tapir.server.netty.internal.FutureUtil import zio.{RIO, ZIO} -object ZioUtil { +private[zio] object ZioUtil { def nettyChannelFutureToScala[R](nettyFuture: ChannelFuture): RIO[R, Channel] = ZIO.fromFuture(_ => FutureUtil.nettyChannelFutureToScala(nettyFuture)) diff --git a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala index ecf61b0146..b69165ac5e 100644 --- a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala +++ b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala @@ -2,11 +2,10 @@ package sttp.tapir.server.netty.zio import io.netty.channel._ import io.netty.channel.unix.DomainSocketAddress -import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.netty.Route import sttp.tapir.server.netty.internal.{NettyBootstrap, NettyServerHandler} -import sttp.tapir.server.netty.zio.ZioUtil.{nettyChannelFutureToScala, nettyFutureToScala} -import sttp.tapir.ztapir.RIOMonadError +import sttp.tapir.server.netty.zio.internal.ZioUtil.{nettyChannelFutureToScala, nettyFutureToScala} +import sttp.tapir.ztapir.{RIOMonadError, ZServerEndpoint} import zio.{RIO, Runtime, ZIO} import java.net.{InetSocketAddress, SocketAddress} @@ -15,17 +14,17 @@ import java.nio.file.Path case class NettyZioServer[R, SA <: SocketAddress](routes: Vector[Route[RIO[R, *]]], options: NettyZioServerOptions[R, SA])(implicit runtime: Runtime[R] ) { - def addEndpoint(se: ServerEndpoint[Any, RIO[R, *]]): NettyZioServer[R, SA] = addEndpoints(List(se)) + def addEndpoint(se: ZServerEndpoint[R, Any]): NettyZioServer[R, SA] = addEndpoints(List(se)) def addEndpoint( - se: ServerEndpoint[Any, RIO[R, *]], + se: ZServerEndpoint[R, Any], overrideOptions: NettyZioServerOptions[R, SA] ): NettyZioServer[R, SA] = addEndpoints(List(se), overrideOptions) - def addEndpoints(ses: List[ServerEndpoint[Any, RIO[R, *]]]): NettyZioServer[R, SA] = addRoute( + def addEndpoints(ses: List[ZServerEndpoint[R, Any]]): NettyZioServer[R, SA] = addRoute( NettyZioServerInterpreter[R](options).toRoute(ses) ) def addEndpoints( - ses: List[ServerEndpoint[Any, RIO[R, *]]], + ses: List[ZServerEndpoint[R, Any]], overrideOptions: NettyZioServerOptions[R, SA] ): NettyZioServer[R, SA] = addRoute( NettyZioServerInterpreter[R](overrideOptions).toRoute(ses) diff --git a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala index 43f39cfb75..7b4b582044 100644 --- a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala +++ b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala @@ -1,15 +1,14 @@ package sttp.tapir.server.netty.zio -import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.netty.Route import sttp.tapir.server.netty.internal.NettyServerInterpreter -import sttp.tapir.ztapir.RIOMonadError +import sttp.tapir.ztapir.{RIOMonadError, ZServerEndpoint} import zio.RIO trait NettyZioServerInterpreter[R] { def nettyServerOptions: NettyZioServerOptions[R, _] - def toRoute(ses: List[ServerEndpoint[Any, RIO[R, *]]]): Route[RIO[R, *]] = { + def toRoute(ses: List[ZServerEndpoint[R, Any]]): Route[RIO[R, *]] = { implicit val monadError: RIOMonadError[R] = new RIOMonadError[R] NettyServerInterpreter.toRoute(ses, nettyServerOptions.interceptors, nettyServerOptions.createFile, nettyServerOptions.deleteFile) } diff --git a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/internal/ZioUtil.scala similarity index 85% rename from server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala rename to server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/internal/ZioUtil.scala index b69f01a9b8..e08cea1d84 100644 --- a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/ZioUtil.scala +++ b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/internal/ZioUtil.scala @@ -1,10 +1,10 @@ -package sttp.tapir.server.netty.zio +package sttp.tapir.server.netty.zio.internal import io.netty.channel.{Channel, ChannelFuture} import sttp.tapir.server.netty.internal.FutureUtil import zio.{RIO, ZIO} -object ZioUtil { +private[zio] object ZioUtil { def nettyChannelFutureToScala[R](nettyFuture: ChannelFuture): RIO[R, Channel] = ZIO.fromFuture(_ => FutureUtil.nettyChannelFutureToScala(nettyFuture)) diff --git a/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala b/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala index 5eac6e8e10..da6e4542e2 100644 --- a/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala +++ b/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala @@ -21,11 +21,8 @@ class NettyZioServerTest extends TestSuite with EitherValues { val interpreter = new NettyZioTestServerInterpreter(eventLoopGroup) val createServerTest = new DefaultCreateServerTest(backend, interpreter) - val tests = - new AllServerTests(createServerTest, interpreter, backend, staticContent = false, multipart = false, basic = false).tests() ++ - // I was unable to came up with why and how this tests fails - probably error cause whole interpreter to crush without sending 500 - new ServerBasicTests(createServerTest, interpreter, invulnerableToUnsanitizedHeaders = false).tests() ++ - new ServerMultipartTests(createServerTest, partOtherHeaderSupport = false).tests() + val tests = new AllServerTests(createServerTest, interpreter, backend, staticContent = false, multipart = false).tests() ++ + new ServerMultipartTests(createServerTest, partOtherHeaderSupport = false).tests() IO.pure((tests, eventLoopGroup)) } { case (_, eventLoopGroup) => From 6ea40e548c219062735810e1dd872e607a240f21 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Fri, 18 Nov 2022 09:38:33 +0100 Subject: [PATCH 008/120] Changes in docs --- doc/index.md | 2 +- doc/server/netty.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/index.md b/doc/index.md index 7eee557f0e..096d883331 100644 --- a/doc/index.md +++ b/doc/index.md @@ -11,7 +11,7 @@ input and output parameters. An endpoint specification can be interpreted as: Currently supported: * [Akka HTTP](server/akkahttp.md) `Route`s/`Directive`s * [Http4s](server/http4s.md) `HttpRoutes[F]` (using cats-effect or [ZIO](server/zio-http4s.md)) - * [Netty](server/netty.md) (using `Future`s or cats-effect) + * [Netty](server/netty.md) (using `Future`s, cats-effect or ZIO) * [Finatra](server/finatra.md) `http.Controller` * [Play](server/play.md) `Route` * [Vert.X](server/vertx.md) `Router => Route` (using `Future`s, cats-effect or ZIO) diff --git a/doc/server/netty.md b/doc/server/netty.md index f0259a6cd9..1148cbdf8c 100644 --- a/doc/server/netty.md +++ b/doc/server/netty.md @@ -8,12 +8,19 @@ To expose an endpoint using a [Netty](https://netty.io)-based server, first add // if you are using cats-effect: "com.softwaremill.sttp.tapir" %% "tapir-netty-server-cats" % "@VERSION@" + +// if you are using zio: +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio" % "@VERSION@" + +// if you are using zio1: +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio1" % "@VERSION@" ``` Then, use: * `NettyFutureServer().addEndpoints` to expose `Future`-based server endpoints. * `NettyCatsServer().addEndpoints` to expose `F`-based server endpoints, where `F` is any cats-effect supported effect. +* `NettyZioServer().addEndpoints` to expose `ZIO`-based server endpoints, where `R` represents ZIO requirements supported effect. These methods require a single, or a list of `ServerEndpoint`s, which can be created by adding [server logic](logic.md) to an endpoint. From 8d80b23de64a4d8c04b07c2c3f98f68b1ccc40c4 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Fri, 18 Nov 2022 11:23:36 +0100 Subject: [PATCH 009/120] Added common config for netty projects --- build.sbt | 61 +++++++++++++++++++------------------------------------ 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/build.sbt b/build.sbt index 7099a524a1..7ed7aadac6 100644 --- a/build.sbt +++ b/build.sbt @@ -179,7 +179,7 @@ lazy val rawAllAggregates = core.projectRefs ++ vertxServerZio1.projectRefs ++ nettyServer.projectRefs ++ nettyServerCats.projectRefs ++ - nettyServerZio.projectRefs++ + nettyServerZio.projectRefs ++ nettyServerZio1.projectRefs ++ zio1HttpServer.projectRefs ++ zioHttpServer.projectRefs ++ @@ -1253,53 +1253,34 @@ lazy val nettyServer: ProjectMatrix = (projectMatrix in file("server/netty-serve .settings(commonJvmSettings) .settings( name := "tapir-netty-server", - libraryDependencies ++= Seq( - "io.netty" % "netty-all" % Versions.nettyAll - ) ++ loggerDependencies, + libraryDependencies ++= Seq("io.netty" % "netty-all" % Versions.nettyAll) + ++ loggerDependencies, // needed because of https://github.com/coursier/coursier/issues/2016 useCoursier := false ) .jvmPlatform(scalaVersions = scala2And3Versions) .dependsOn(serverCore, serverTests % Test) -lazy val nettyServerCats: ProjectMatrix = (projectMatrix in file("server/netty-server/cats")) - .settings(commonJvmSettings) - .settings( - name := "tapir-netty-server-cats", - libraryDependencies ++= Seq( - "com.softwaremill.sttp.shared" %% "fs2" % Versions.sttpShared - ) ++ loggerDependencies, - // needed because of https://github.com/coursier/coursier/issues/2016 - useCoursier := false - ) - .jvmPlatform(scalaVersions = scala2And3Versions) - .dependsOn(nettyServer, cats, serverTests % Test) +lazy val nettyServerCats: ProjectMatrix = nettyServerProject("cats", cats) + .settings(libraryDependencies += "com.softwaremill.sttp.shared" %% "fs2" % Versions.sttpShared) -lazy val nettyServerZio: ProjectMatrix = (projectMatrix in file("server/netty-server/zio")) - .settings(commonJvmSettings) - .settings( - name := "tapir-netty-server-zio", - libraryDependencies ++= Seq( - "dev.zio" %% "zio-interop-cats" % Versions.zioInteropCats, - ) ++ loggerDependencies, - // needed because of https://github.com/coursier/coursier/issues/2016 - useCoursier := false - ) - .jvmPlatform(scalaVersions = scala2And3Versions) - .dependsOn(nettyServer, zio, serverTests % Test) +lazy val nettyServerZio: ProjectMatrix = nettyServerProject("zio", zio) + .settings(libraryDependencies += "dev.zio" %% "zio-interop-cats" % Versions.zioInteropCats) -lazy val nettyServerZio1: ProjectMatrix = (projectMatrix in file("server/netty-server/zio1")) - .settings(commonJvmSettings) - .settings( - name := "tapir-netty-server-zio1", - libraryDependencies ++= Seq( - "dev.zio" %% "zio-interop-cats" % Versions.zio1InteropCats, - ) ++ loggerDependencies, - // needed because of https://github.com/coursier/coursier/issues/2016 - useCoursier := false - ) - .jvmPlatform(scalaVersions = scala2And3Versions) - .dependsOn(nettyServer, zio1, serverTests % Test) +lazy val nettyServerZio1: ProjectMatrix = nettyServerProject("zio1", zio1) + .settings(libraryDependencies += "dev.zio" %% "zio-interop-cats" % Versions.zio1InteropCats) + +def nettyServerProject(proj: String, dependency: ProjectMatrix): ProjectMatrix = + ProjectMatrix(s"nettyServer${proj.capitalize}", file(s"server/netty-server/$proj")) + .settings(commonJvmSettings) + .settings( + name := s"tapir-netty-server-$proj", + libraryDependencies ++= loggerDependencies, + // needed because of https://github.com/coursier/coursier/issues/2016 + useCoursier := false + ) + .jvmPlatform(scalaVersions = scala2And3Versions) + .dependsOn(nettyServer, dependency, serverTests % Test) lazy val vertxServer: ProjectMatrix = (projectMatrix in file("server/vertx-server")) .settings(commonJvmSettings) From 30633ec543f13d45f8540174f699b338a1427aaf Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Wed, 30 Nov 2022 11:58:10 +0100 Subject: [PATCH 010/120] Added runAsync --- .../netty/zio/NettyZioServerInterpreter.scala | 17 ++++++++++++---- .../server/netty/zio/NettyZioServerTest.scala | 3 +-- .../netty/zio/NettyZioServerInterpreter.scala | 20 +++++++++++++++---- .../server/netty/zio/NettyZioServerTest.scala | 3 +-- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala index 435fc4084c..9d2384bf62 100644 --- a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala @@ -1,17 +1,19 @@ package sttp.tapir.server.netty.zio import sttp.tapir.server.netty.Route -import sttp.tapir.server.netty.internal.NettyServerInterpreter +import sttp.tapir.server.netty.internal.{NettyServerInterpreter, RunAsync} +import sttp.tapir.server.netty.zio.NettyZioServerInterpreter.ZioRunAsync import sttp.tapir.ztapir.{RIOMonadError, ZServerEndpoint} -import zio.RIO +import zio.{CancelableFuture, RIO, Runtime, Unsafe} trait NettyZioServerInterpreter[R] { def nettyServerOptions: NettyZioServerOptions[R, _] - def toRoute(ses: List[ZServerEndpoint[R, Any]]): Route[RIO[R, *]] = { + def toRoute(ses: List[ZServerEndpoint[R, Any]])(implicit runtime: Runtime[R]): Route[RIO[R, *]] = { implicit val monadError: RIOMonadError[R] = new RIOMonadError[R] + val runAsync = new ZioRunAsync(runtime) NettyServerInterpreter - .toRoute(ses, nettyServerOptions.interceptors, nettyServerOptions.createFile, nettyServerOptions.deleteFile) + .toRoute(ses, nettyServerOptions.interceptors, nettyServerOptions.createFile, nettyServerOptions.deleteFile, runAsync) // we want to log & return a 500 in case of defects as well .andThen(_.resurrect) } @@ -28,4 +30,11 @@ object NettyZioServerInterpreter { override def nettyServerOptions: NettyZioServerOptions[R, _] = options } } + + private[netty] class ZioRunAsync[R](runtime: Runtime[R]) extends RunAsync[RIO[R, *]] { + override def apply[T](f: => RIO[R, T]): Unit = Unsafe.unsafe(implicit u => { + val value: CancelableFuture[T] = runtime.unsafe.runToFuture(f) + value + }) + } } diff --git a/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala b/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala index 11fbe53830..8d1137d7e4 100644 --- a/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala +++ b/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala @@ -22,8 +22,7 @@ class NettyZioServerTest extends TestSuite with EitherValues { val createServerTest = new DefaultCreateServerTest(backend, interpreter) val tests = - new AllServerTests(createServerTest, interpreter, backend, staticContent = false, multipart = false).tests() ++ - new ServerMultipartTests(createServerTest, partOtherHeaderSupport = false).tests() + new AllServerTests(createServerTest, interpreter, backend, staticContent = false, multipart = false).tests() IO.pure((tests, eventLoopGroup)) } { case (_, eventLoopGroup) => diff --git a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala index 7b4b582044..616bc468ee 100644 --- a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala +++ b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala @@ -1,16 +1,24 @@ package sttp.tapir.server.netty.zio import sttp.tapir.server.netty.Route -import sttp.tapir.server.netty.internal.NettyServerInterpreter +import sttp.tapir.server.netty.internal.{NettyServerInterpreter, RunAsync} +import sttp.tapir.server.netty.zio.NettyZioServerInterpreter.ZioRunAsync import sttp.tapir.ztapir.{RIOMonadError, ZServerEndpoint} -import zio.RIO +import zio.{RIO, Runtime} trait NettyZioServerInterpreter[R] { def nettyServerOptions: NettyZioServerOptions[R, _] - def toRoute(ses: List[ZServerEndpoint[R, Any]]): Route[RIO[R, *]] = { + def toRoute(ses: List[ZServerEndpoint[R, Any]])(implicit runtime: Runtime[R]): Route[RIO[R, *]] = { implicit val monadError: RIOMonadError[R] = new RIOMonadError[R] - NettyServerInterpreter.toRoute(ses, nettyServerOptions.interceptors, nettyServerOptions.createFile, nettyServerOptions.deleteFile) + val runAsync = new ZioRunAsync(runtime) + NettyServerInterpreter.toRoute( + ses, + nettyServerOptions.interceptors, + nettyServerOptions.createFile, + nettyServerOptions.deleteFile, + runAsync + ) } } @@ -25,4 +33,8 @@ object NettyZioServerInterpreter { override def nettyServerOptions: NettyZioServerOptions[R, _] = options } } + + private[netty] class ZioRunAsync[R](runtime: Runtime[R]) extends RunAsync[RIO[R, *]] { + override def apply[T](f: => RIO[R, T]): Unit = runtime.unsafeRunAsync(f)(_ => ()) + } } diff --git a/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala b/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala index da6e4542e2..8144db4466 100644 --- a/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala +++ b/server/netty-server/zio1/src/test/scala/sttp/tapir/server/netty/zio/NettyZioServerTest.scala @@ -21,8 +21,7 @@ class NettyZioServerTest extends TestSuite with EitherValues { val interpreter = new NettyZioTestServerInterpreter(eventLoopGroup) val createServerTest = new DefaultCreateServerTest(backend, interpreter) - val tests = new AllServerTests(createServerTest, interpreter, backend, staticContent = false, multipart = false).tests() ++ - new ServerMultipartTests(createServerTest, partOtherHeaderSupport = false).tests() + val tests = new AllServerTests(createServerTest, interpreter, backend, staticContent = false, multipart = false).tests() IO.pure((tests, eventLoopGroup)) } { case (_, eventLoopGroup) => From f127e1bec5b2f5cf263e0dc66673d4e72f8737f0 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Wed, 30 Nov 2022 12:17:03 +0100 Subject: [PATCH 011/120] Remove redundant val --- .../tapir/server/netty/zio/NettyZioServerInterpreter.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala index 9d2384bf62..bdce856c60 100644 --- a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala @@ -32,9 +32,6 @@ object NettyZioServerInterpreter { } private[netty] class ZioRunAsync[R](runtime: Runtime[R]) extends RunAsync[RIO[R, *]] { - override def apply[T](f: => RIO[R, T]): Unit = Unsafe.unsafe(implicit u => { - val value: CancelableFuture[T] = runtime.unsafe.runToFuture(f) - value - }) + override def apply[T](f: => RIO[R, T]): Unit = Unsafe.unsafe(implicit u => runtime.unsafe.runToFuture(f)) } } From 7cd79a1cf69cd2465841062abeb4b5ea988426c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Or=C5=82owski?= Date: Thu, 1 Dec 2022 11:34:12 +0100 Subject: [PATCH 012/120] Add support for Json Query Params --- .../main/scala/sttp/tapir/EndpointIO.scala | 6 ++--- core/src/main/scala/sttp/tapir/Tapir.scala | 5 +++- .../test/scala/sttp/tapir/EndpointTest.scala | 2 +- .../EndpointInputToParameterConverter.scala | 17 +++++++++++- .../docs/openapi/EndpointToOpenAPIPaths.scala | 5 +++- .../resources/expected_json_query_param.yml | 27 +++++++++++++++++++ .../tapir/docs/openapi/VerifyYamlTest.scala | 14 ++++++++++ .../tapir/json/circe/TapirJsonCirce.scala | 3 +++ .../sttp/tapir/json/json4s/TapirJson4s.scala | 5 +++- .../json/jsoniter/TapirJsonJsoniter.scala | 7 +++-- .../sttp/tapir/json/play/TapirJsonPlay.scala | 3 +++ .../tapir/json/spray/TapirJsonSpray.scala | 4 ++- .../tapir/json/circe/TapirJsonTethys.scala | 3 +++ .../tapir/json/upickle/TapirJsonuPickle.scala | 3 +++ .../sttp/tapir/json/zio/TapirJsonZio.scala | 5 +++- .../sttp/tapir/json/zio/TapirJsonZio.scala | 5 +++- 16 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 docs/openapi-docs/src/test/resources/expected_json_query_param.yml diff --git a/core/src/main/scala/sttp/tapir/EndpointIO.scala b/core/src/main/scala/sttp/tapir/EndpointIO.scala index ea8b2a1428..0224cb625c 100644 --- a/core/src/main/scala/sttp/tapir/EndpointIO.scala +++ b/core/src/main/scala/sttp/tapir/EndpointIO.scala @@ -196,11 +196,11 @@ object EndpointInput extends EndpointInputMacros { override def show = s"/*" } - case class Query[T](name: String, flagValue: Option[T], codec: Codec[List[String], T, TextPlain], info: Info[T]) extends Atom[T] { + case class Query[T](name: String, flagValue: Option[T], codec: Codec[List[String], T, CodecFormat], info: Info[T]) extends Atom[T] { override private[tapir] type ThisType[X] = Query[X] override private[tapir] type L = List[String] - override private[tapir] type CF = TextPlain - override private[tapir] def copyWith[U](c: Codec[List[String], U, TextPlain], i: Info[U]): Query[U] = + override private[tapir] type CF = CodecFormat + override private[tapir] def copyWith[U](c: Codec[List[String], U, CodecFormat], i: Info[U]): Query[U] = copy(flagValue = flagValue.map(t => c.decode(codec.encode(t))).collect { case DecodeResult.Value(u) => u }, codec = c, info = i) override def show: String = addValidatorShow(s"?$name", codec.schema) diff --git a/core/src/main/scala/sttp/tapir/Tapir.scala b/core/src/main/scala/sttp/tapir/Tapir.scala index 97274e26f9..c24c888fd4 100644 --- a/core/src/main/scala/sttp/tapir/Tapir.scala +++ b/core/src/main/scala/sttp/tapir/Tapir.scala @@ -27,8 +27,11 @@ trait Tapir extends TapirExtensions with TapirComputedInputs with TapirStaticCon EndpointInput.PathCapture(Some(name), implicitly, EndpointIO.Info.empty) def paths: EndpointInput.PathsCapture[List[String]] = EndpointInput.PathsCapture(Codec.idPlain(), EndpointIO.Info.empty) + /** A query parameter in any format, read using the given `codec`. */ + def anyQuery[T, CF <: CodecFormat](name: String, codec: Codec[List[String], T, CF]): EndpointInput.Query[T] = + EndpointInput.Query(name, None, codec, EndpointIO.Info.empty) def query[T: Codec[List[String], *, TextPlain]](name: String): EndpointInput.Query[T] = - EndpointInput.Query(name, None, implicitly, EndpointIO.Info.empty) + anyQuery[T, TextPlain](name, implicitly) def queryParams: EndpointInput.QueryParams[QueryParams] = EndpointInput.QueryParams(Codec.idPlain(), EndpointIO.Info.empty) def header[T: Codec[List[String], *, TextPlain]](name: String): EndpointIO.Header[T] = diff --git a/core/src/test/scala/sttp/tapir/EndpointTest.scala b/core/src/test/scala/sttp/tapir/EndpointTest.scala index 5a68c74fce..74e72fea32 100644 --- a/core/src/test/scala/sttp/tapir/EndpointTest.scala +++ b/core/src/test/scala/sttp/tapir/EndpointTest.scala @@ -374,7 +374,7 @@ class EndpointTest extends AnyFlatSpec with EndpointTestExtensions with Matchers // given case class Wrapper(i: Int) val mapped = query[Int]("q1").mapTo[Wrapper] - val codec: Codec[List[String], Wrapper, CodecFormat.TextPlain] = mapped.codec + val codec: Codec[List[String], Wrapper, CodecFormat] = mapped.codec // when codec.encode(Wrapper(10)) shouldBe (List("10")) diff --git a/docs/openapi-docs/src/main/scala/sttp/tapir/docs/openapi/EndpointInputToParameterConverter.scala b/docs/openapi-docs/src/main/scala/sttp/tapir/docs/openapi/EndpointInputToParameterConverter.scala index bc77d1981b..164427c709 100644 --- a/docs/openapi-docs/src/main/scala/sttp/tapir/docs/openapi/EndpointInputToParameterConverter.scala +++ b/docs/openapi-docs/src/main/scala/sttp/tapir/docs/openapi/EndpointInputToParameterConverter.scala @@ -1,11 +1,13 @@ package sttp.tapir.docs.openapi import sttp.apispec.{ReferenceOr, Schema} -import sttp.apispec.openapi.{Parameter, ParameterIn} +import sttp.apispec.openapi.{MediaType, Parameter, ParameterIn} import sttp.tapir.docs.apispec.DocsExtensionAttribute.RichEndpointIOInfo import sttp.tapir.docs.apispec.DocsExtensions import sttp.tapir.{Codec, EndpointIO, EndpointInput} +import scala.collection.immutable.ListMap + private[openapi] object EndpointInputToParameterConverter { def from[T](query: EndpointInput.Query[T], schema: ReferenceOr[Schema]): Parameter = { val examples = ExampleConverter.convertExamples(query.codec, query.info.examples) @@ -24,6 +26,19 @@ private[openapi] object EndpointInputToParameterConverter { ) } + def from[T](query: EndpointInput.Query[T], content: ListMap[String, MediaType]): Parameter = + Parameter( + name = query.name, + in = ParameterIn.Query, + description = query.info.description, + required = Some(!query.codec.schema.isOptional), + deprecated = if (query.info.deprecated) Some(true) else None, + schema = None, + extensions = DocsExtensions.fromIterable(query.info.docsExtensions), + content = content, + allowEmptyValue = query.flagValue.fold(None: Option[Boolean])(_ => Some(true)) + ) + def from[T](pathCapture: EndpointInput.PathCapture[T], schema: ReferenceOr[Schema]): Parameter = { val examples = ExampleConverter.convertExamples(pathCapture.codec, pathCapture.info.examples) Parameter( diff --git a/docs/openapi-docs/src/main/scala/sttp/tapir/docs/openapi/EndpointToOpenAPIPaths.scala b/docs/openapi-docs/src/main/scala/sttp/tapir/docs/openapi/EndpointToOpenAPIPaths.scala index 55ba8fd738..20230ed6f5 100644 --- a/docs/openapi-docs/src/main/scala/sttp/tapir/docs/openapi/EndpointToOpenAPIPaths.scala +++ b/docs/openapi-docs/src/main/scala/sttp/tapir/docs/openapi/EndpointToOpenAPIPaths.scala @@ -108,7 +108,10 @@ private[openapi] class EndpointToOpenAPIPaths(schemas: Schemas, securitySchemes: EndpointInputToParameterConverter.from(header, Right(ASchema(ASchemaType.String))) private def cookieToParameter[T](cookie: EndpointInput.Cookie[T]) = EndpointInputToParameterConverter.from(cookie, schemas(cookie.codec)) private def pathCaptureToParameter[T](p: EndpointInput.PathCapture[T]) = EndpointInputToParameterConverter.from(p, schemas(p.codec)) - private def queryToParameter[T](query: EndpointInput.Query[T]) = EndpointInputToParameterConverter.from(query, schemas(query.codec)) + private def queryToParameter[T](query: EndpointInput.Query[T]) = query.codec.format match { + case CodecFormat.TextPlain() => EndpointInputToParameterConverter.from(query, schemas(query.codec)) + case _ => EndpointInputToParameterConverter.from(query, codecToMediaType(query.codec, query.info.examples, None, Nil)) + } private def enrich(e: EndpointInput.Atom[_], p: Parameter): Parameter = addExplode(e, p) diff --git a/docs/openapi-docs/src/test/resources/expected_json_query_param.yml b/docs/openapi-docs/src/test/resources/expected_json_query_param.yml new file mode 100644 index 0000000000..7aca12008b --- /dev/null +++ b/docs/openapi-docs/src/test/resources/expected_json_query_param.yml @@ -0,0 +1,27 @@ +openapi: 3.0.3 +info: + title: Entities + version: '1.0' +paths: + /: + post: + operationId: postRoot + parameters: + - name: name + in: query + required: false + content: + application/json: + schema: + type: string + default: tom + example: alan + responses: + '200': + description: '' + '400': + description: 'Invalid value for: query parameter name' + content: + text/plain: + schema: + type: string diff --git a/docs/openapi-docs/src/test/scalajvm/sttp/tapir/docs/openapi/VerifyYamlTest.scala b/docs/openapi-docs/src/test/scalajvm/sttp/tapir/docs/openapi/VerifyYamlTest.scala index 56a5e47c78..d5c52259e6 100644 --- a/docs/openapi-docs/src/test/scalajvm/sttp/tapir/docs/openapi/VerifyYamlTest.scala +++ b/docs/openapi-docs/src/test/scalajvm/sttp/tapir/docs/openapi/VerifyYamlTest.scala @@ -781,6 +781,20 @@ class VerifyYamlTest extends AnyFunSuite with Matchers { noIndentation(actualYaml) shouldBe expectedYaml } + + test("should add application/json content for json query parameter") { + val expectedYaml = load("expected_json_query_param.yml") + val codec = Codec.listHead(Codec.json[String](DecodeResult.Value(_))(identity)) + val actualYaml = OpenAPIDocsInterpreter() + .toOpenAPI( + endpoint.post.in(anyQuery[String, CodecFormat.Json]("name", codec).example("alan").default("tom")), + Info("Entities", "1.0") + ) + .toYaml + + val actualYamlNoIndent = noIndentation(actualYaml) + actualYamlNoIndent shouldBe expectedYaml + } } object VerifyYamlTest { diff --git a/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala b/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala index 180284b725..b1f42a4f7f 100644 --- a/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala +++ b/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala @@ -17,6 +17,9 @@ trait TapirJsonCirce { implicitly[JsonCodec[(String, T)]] ) + def jsonQuery[T: Encoder: Decoder: Schema](name: String): EndpointInput.Query[T] = + anyQuery[T, CodecFormat.Json](name, implicitly) + implicit def circeCodec[T: Encoder: Decoder: Schema]: JsonCodec[T] = sttp.tapir.Codec.json[T] { s => io.circe.parser.decodeAccumulating[T](s) match { diff --git a/json/json4s/src/main/scala/sttp/tapir/json/json4s/TapirJson4s.scala b/json/json4s/src/main/scala/sttp/tapir/json/json4s/TapirJson4s.scala index 79baeebf13..a25c905855 100644 --- a/json/json4s/src/main/scala/sttp/tapir/json/json4s/TapirJson4s.scala +++ b/json/json4s/src/main/scala/sttp/tapir/json/json4s/TapirJson4s.scala @@ -5,7 +5,7 @@ import sttp.tapir.Codec.JsonCodec import sttp.tapir.DecodeResult.Error.{JsonDecodeException, JsonError} import sttp.tapir.DecodeResult.{Error, Value} import sttp.tapir.SchemaType.SCoproduct -import sttp.tapir.{Codec, EndpointIO, Schema, stringBodyUtf8AnyFormat} +import sttp.tapir._ trait TapirJson4s { def jsonBody[T: Manifest: Schema](implicit formats: Formats, serialization: Serialization): EndpointIO.Body[String, T] = @@ -16,6 +16,9 @@ trait TapirJson4s { implicitly[JsonCodec[(String, T)]] ) + def jsonQuery[T: Manifest: Schema](name: String)(implicit formats: Formats, serialization: Serialization): EndpointInput.Query[T] = + anyQuery[T, CodecFormat.Json](name, implicitly) + implicit def json4sCodec[T: Manifest: Schema](implicit formats: Formats, serialization: Serialization): JsonCodec[T] = Codec.json[T] { s => try { diff --git a/json/jsoniter/src/main/scala/sttp/tapir/json/jsoniter/TapirJsonJsoniter.scala b/json/jsoniter/src/main/scala/sttp/tapir/json/jsoniter/TapirJsonJsoniter.scala index 4b7f80f780..b0a26e5c09 100644 --- a/json/jsoniter/src/main/scala/sttp/tapir/json/jsoniter/TapirJsonJsoniter.scala +++ b/json/jsoniter/src/main/scala/sttp/tapir/json/jsoniter/TapirJsonJsoniter.scala @@ -1,10 +1,10 @@ package sttp.tapir.json.jsoniter -import com.github.plokhotnyuk.jsoniter_scala.core.{JsonValueCodec, _} +import com.github.plokhotnyuk.jsoniter_scala.core._ import sttp.tapir.Codec.JsonCodec import sttp.tapir.DecodeResult.Error.JsonDecodeException import sttp.tapir.DecodeResult.{Error, Value} -import sttp.tapir.{EndpointIO, Schema, stringBodyUtf8AnyFormat} +import sttp.tapir._ import scala.util.{Failure, Success, Try} @@ -15,6 +15,9 @@ trait TapirJsonJsoniter { implicitly[JsonCodec[(String, T)]] ) + def jsonQuery[T: JsonValueCodec: Schema](name: String): EndpointInput.Query[T] = + anyQuery[T, CodecFormat.Json](name, implicitly) + implicit def jsoniterCodec[T: JsonValueCodec: Schema]: JsonCodec[T] = sttp.tapir.Codec.json { s => Try(readFromString[T](s)) match { diff --git a/json/playjson/src/main/scala/sttp/tapir/json/play/TapirJsonPlay.scala b/json/playjson/src/main/scala/sttp/tapir/json/play/TapirJsonPlay.scala index 3a468c3566..9a17403ccc 100644 --- a/json/playjson/src/main/scala/sttp/tapir/json/play/TapirJsonPlay.scala +++ b/json/playjson/src/main/scala/sttp/tapir/json/play/TapirJsonPlay.scala @@ -17,6 +17,9 @@ trait TapirJsonPlay { implicitly[JsonCodec[(String, T)]] ) + def jsonQuery[T: Reads: Writes: Schema](name: String): EndpointInput.Query[T] = + anyQuery[T, CodecFormat.Json](name, implicitly) + implicit def readsWritesCodec[T: Reads: Writes: Schema]: JsonCodec[T] = Codec.json[T] { s => Try(Json.parse(s)) match { diff --git a/json/sprayjson/src/main/scala/sttp/tapir/json/spray/TapirJsonSpray.scala b/json/sprayjson/src/main/scala/sttp/tapir/json/spray/TapirJsonSpray.scala index 4ee306810b..b5b50e52ba 100644 --- a/json/sprayjson/src/main/scala/sttp/tapir/json/spray/TapirJsonSpray.scala +++ b/json/sprayjson/src/main/scala/sttp/tapir/json/spray/TapirJsonSpray.scala @@ -8,7 +8,6 @@ import sttp.tapir.Schema.SName import sttp.tapir.SchemaType._ import sttp.tapir._ -import scala.collection.immutable.ListMap import scala.util.{Failure, Success, Try} trait TapirJsonSpray { @@ -18,6 +17,9 @@ trait TapirJsonSpray { implicitly[JsonCodec[(String, T)]] ) + def jsonQuery[T: JsonFormat: Schema](name: String): EndpointInput.Query[T] = + anyQuery[T, CodecFormat.Json](name, implicitly) + implicit def jsonFormatCodec[T: JsonFormat: Schema]: JsonCodec[T] = Codec.json { s => Try(s.parseJson.convertTo[T]) match { diff --git a/json/tethys/src/main/scala/sttp/tapir/json/circe/TapirJsonTethys.scala b/json/tethys/src/main/scala/sttp/tapir/json/circe/TapirJsonTethys.scala index 5d9e60b630..f7c58653f9 100644 --- a/json/tethys/src/main/scala/sttp/tapir/json/circe/TapirJsonTethys.scala +++ b/json/tethys/src/main/scala/sttp/tapir/json/circe/TapirJsonTethys.scala @@ -14,6 +14,9 @@ trait TapirJsonTethys { implicitly[JsonCodec[(String, T)]] ) + def jsonQuery[T: JsonWriter: JsonReader: Schema](name: String): EndpointInput.Query[T] = + anyQuery[T, CodecFormat.Json](name, implicitly) + implicit def tethysCodec[T: JsonReader: JsonWriter: Schema]: JsonCodec[T] = Codec.json(s => s.jsonAs[T] match { diff --git a/json/upickle/src/main/scala/sttp/tapir/json/upickle/TapirJsonuPickle.scala b/json/upickle/src/main/scala/sttp/tapir/json/upickle/TapirJsonuPickle.scala index 893e2fa56e..e321d4a93c 100644 --- a/json/upickle/src/main/scala/sttp/tapir/json/upickle/TapirJsonuPickle.scala +++ b/json/upickle/src/main/scala/sttp/tapir/json/upickle/TapirJsonuPickle.scala @@ -15,6 +15,9 @@ trait TapirJsonuPickle { implicitly[JsonCodec[(String, T)]] ) + def jsonQuery[T: ReadWriter: Schema](name: String): EndpointInput.Query[T] = + anyQuery[T, CodecFormat.Json](name, implicitly) + implicit def readWriterCodec[T: ReadWriter: Schema]: JsonCodec[T] = Codec.json[T] { s => Try(read[T](s)) match { diff --git a/json/zio/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala b/json/zio/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala index b00674a4d1..06bece5f5d 100644 --- a/json/zio/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala +++ b/json/zio/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala @@ -5,7 +5,7 @@ import sttp.tapir.DecodeResult.Error.{JsonDecodeException, JsonError} import sttp.tapir.DecodeResult.{Error, Value} import sttp.tapir.Schema.SName import sttp.tapir.SchemaType.{SCoproduct, SProduct} -import sttp.tapir.{EndpointIO, FieldName, Schema, stringBodyUtf8AnyFormat} +import sttp.tapir._ import zio.json.ast.Json import zio.json.ast.Json.Obj import zio.json.{JsonDecoder, JsonEncoder, _} @@ -18,6 +18,9 @@ trait TapirJsonZio { implicitly[JsonCodec[(String, T)]] ) + def jsonQuery[T: JsonEncoder: JsonDecoder: Schema](name: String): EndpointInput.Query[T] = + anyQuery[T, CodecFormat.Json](name, implicitly) + implicit def zioCodec[T: JsonEncoder: JsonDecoder: Schema]: JsonCodec[T] = sttp.tapir.Codec.json[T] { s => zio.json.JsonDecoder.apply[T].decodeJson(s) match { diff --git a/json/zio1/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala b/json/zio1/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala index b00674a4d1..06bece5f5d 100644 --- a/json/zio1/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala +++ b/json/zio1/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala @@ -5,7 +5,7 @@ import sttp.tapir.DecodeResult.Error.{JsonDecodeException, JsonError} import sttp.tapir.DecodeResult.{Error, Value} import sttp.tapir.Schema.SName import sttp.tapir.SchemaType.{SCoproduct, SProduct} -import sttp.tapir.{EndpointIO, FieldName, Schema, stringBodyUtf8AnyFormat} +import sttp.tapir._ import zio.json.ast.Json import zio.json.ast.Json.Obj import zio.json.{JsonDecoder, JsonEncoder, _} @@ -18,6 +18,9 @@ trait TapirJsonZio { implicitly[JsonCodec[(String, T)]] ) + def jsonQuery[T: JsonEncoder: JsonDecoder: Schema](name: String): EndpointInput.Query[T] = + anyQuery[T, CodecFormat.Json](name, implicitly) + implicit def zioCodec[T: JsonEncoder: JsonDecoder: Schema]: JsonCodec[T] = sttp.tapir.Codec.json[T] { s => zio.json.JsonDecoder.apply[T].decodeJson(s) match { From 09aea6f5006e1264486f2fb749aaa36892f21c92 Mon Sep 17 00:00:00 2001 From: Massimo Siani Date: Sat, 3 Dec 2022 16:08:41 +0100 Subject: [PATCH 013/120] configure aws sam environment --- .../scala/sttp/tapir/serverless/aws/sam/AwsSamOptions.scala | 3 ++- .../tapir/serverless/aws/sam/AwsSamTemplateEncoders.scala | 1 + .../tapir/serverless/aws/sam/EndpointsToSamTemplate.scala | 5 +++-- .../src/main/scala/sttp/tapir/serverless/aws/sam/model.scala | 5 ++++- serverless/aws/sam/src/test/resources/http_api_template.yaml | 4 ++++ .../tapir/serverless/aws/sam/VerifySamTemplateTest.scala | 2 +- 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/AwsSamOptions.scala b/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/AwsSamOptions.scala index 4f3262d4c8..369d4e0450 100644 --- a/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/AwsSamOptions.scala +++ b/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/AwsSamOptions.scala @@ -15,7 +15,8 @@ case class AwsSamOptions( sealed trait FunctionSource case class ImageSource(imageUri: String) extends FunctionSource -case class CodeSource(runtime: String, codeUri: String, handler: String) extends FunctionSource +case class CodeSource(runtime: String, codeUri: String, handler: String, environment: Map[String, String] = Map.empty) + extends FunctionSource case class HttpApiProperties(cors: Option[Cors]) object HttpApiProperties { diff --git a/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/AwsSamTemplateEncoders.scala b/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/AwsSamTemplateEncoders.scala index 715d8a1543..3d5aba8f92 100644 --- a/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/AwsSamTemplateEncoders.scala +++ b/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/AwsSamTemplateEncoders.scala @@ -21,6 +21,7 @@ object AwsSamTemplateEncoders { } implicit val encoderCorsConfiguration: Encoder[CorsConfiguration] = deriveEncoder[CorsConfiguration] + implicit val encoderEnvironmentCodeProperties: Encoder[EnvironmentCodeProperties] = deriveEncoder[EnvironmentCodeProperties] implicit val encoderHttpProperties: Encoder[HttpProperties] = deriveEncoder[HttpProperties] implicit val encoderFunctionImageProperties: Encoder[FunctionImageProperties] = deriveEncoder[FunctionImageProperties] implicit val encoderFunctionCodeProperties: Encoder[FunctionCodeProperties] = deriveEncoder[FunctionCodeProperties] diff --git a/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/EndpointsToSamTemplate.scala b/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/EndpointsToSamTemplate.scala index c49b94ef6e..101aadfa8b 100644 --- a/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/EndpointsToSamTemplate.scala +++ b/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/EndpointsToSamTemplate.scala @@ -24,14 +24,15 @@ private[sam] object EndpointsToSamTemplate { options.source match { case ImageSource(imageUri) => FunctionImageProperties(options.timeout.toSeconds, options.memorySize, apiEvents, imageUri) - case cs @ CodeSource(_, _, _) => + case cs @ CodeSource(_, _, _, environment) => FunctionCodeProperties( options.timeout.toSeconds, options.memorySize, apiEvents, cs.runtime, cs.codeUri, - cs.handler + cs.handler, + Environment = if (environment.nonEmpty) Some(EnvironmentCodeProperties(environment)) else None ) } ), diff --git a/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/model.scala b/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/model.scala index 30e0ffb390..8c65024a37 100644 --- a/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/model.scala +++ b/serverless/aws/sam/src/main/scala/sttp/tapir/serverless/aws/sam/model.scala @@ -41,7 +41,8 @@ case class FunctionCodeProperties( Events: Map[String, FunctionHttpApiEvent], Runtime: String, CodeUri: String, - Handler: String + Handler: String, + Environment: Option[EnvironmentCodeProperties] ) extends Properties with FunctionProperties @@ -67,3 +68,5 @@ case class CorsConfiguration( ExposeHeaders: Option[Set[String]], MaxAge: Option[Long] ) + +case class EnvironmentCodeProperties(Variables: Map[String, String]) diff --git a/serverless/aws/sam/src/test/resources/http_api_template.yaml b/serverless/aws/sam/src/test/resources/http_api_template.yaml index 96e037e13b..5669689d9e 100644 --- a/serverless/aws/sam/src/test/resources/http_api_template.yaml +++ b/serverless/aws/sam/src/test/resources/http_api_template.yaml @@ -33,6 +33,10 @@ Resources: Runtime: java11 CodeUri: /somewhere/pet-api.jar Handler: pet.api.Handler::handleRequest + Environment: + Variables: + myEnv: foo + otherEnv: bar Type: AWS::Serverless::Function PetApiHttpApi: Properties: diff --git a/serverless/aws/sam/src/test/scala/sttp/tapir/serverless/aws/sam/VerifySamTemplateTest.scala b/serverless/aws/sam/src/test/scala/sttp/tapir/serverless/aws/sam/VerifySamTemplateTest.scala index 214ba513be..bbaeed26b3 100644 --- a/serverless/aws/sam/src/test/scala/sttp/tapir/serverless/aws/sam/VerifySamTemplateTest.scala +++ b/serverless/aws/sam/src/test/scala/sttp/tapir/serverless/aws/sam/VerifySamTemplateTest.scala @@ -61,7 +61,7 @@ class VerifySamTemplateTest extends AnyFunSuite with Matchers { ) ) ), - source = CodeSource(runtime = "java11", codeUri = "/somewhere/pet-api.jar", "pet.api.Handler::handleRequest"), + source = CodeSource(runtime = "java11", codeUri = "/somewhere/pet-api.jar", "pet.api.Handler::handleRequest", environment = Map("myEnv" -> "foo", "otherEnv" -> "bar")), memorySize = 1024 ) From b4388e13ee7a2ab79ac09a40cddcf4518ca35ae5 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sun, 4 Dec 2022 00:06:18 +0000 Subject: [PATCH 014/120] Update enumeratum to 1.7.1 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 9d455cf37e..69451e09ba 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -23,7 +23,7 @@ object Versions { val scalaTest = "3.2.14" val scalaTestPlusScalaCheck = "3.2.14.0" val refined = "0.10.1" - val enumeratum = "1.7.0" + val enumeratum = "1.7.1" val zio1 = "1.0.17" val zio1InteropCats = "3.2.9.1" val zio1Json = "0.2.0" From b25735245393a33ad12b7a2d568297df5bed1544 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Mon, 5 Dec 2022 00:14:34 +0000 Subject: [PATCH 015/120] Update enumeratum to 1.7.2 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 69451e09ba..0e07bf1e99 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -23,7 +23,7 @@ object Versions { val scalaTest = "3.2.14" val scalaTestPlusScalaCheck = "3.2.14.0" val refined = "0.10.1" - val enumeratum = "1.7.1" + val enumeratum = "1.7.2" val zio1 = "1.0.17" val zio1InteropCats = "3.2.9.1" val zio1Json = "0.2.0" From 347f0fe29ec94891b6e42e51f6e08527aafe83ac Mon Sep 17 00:00:00 2001 From: scala-steward Date: Mon, 5 Dec 2022 00:14:43 +0000 Subject: [PATCH 016/120] Update client3:akka-http-backend, ... to 3.8.5 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 69451e09ba..89848f786e 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -6,7 +6,7 @@ object Versions { val circe = "0.14.3" val circeGenericExtras = "0.14.3" val circeYaml = "0.14.2" - val sttp = "3.8.3" + val sttp = "3.8.5" val sttpModel = "1.5.3" val sttpShared = "1.3.12" val sttpApispec = "0.3.1" From 32293a937fee905c7d4c075e5252746f5164b49b Mon Sep 17 00:00:00 2001 From: scala-steward Date: Mon, 5 Dec 2022 00:15:02 +0000 Subject: [PATCH 017/120] Update httpmime to 4.5.14 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 1c873bd83e..e7c2fff4dc 100644 --- a/build.sbt +++ b/build.sbt @@ -1209,7 +1209,7 @@ lazy val finatraServer: ProjectMatrix = (projectMatrix in file("server/finatra-s name := "tapir-finatra-server", libraryDependencies ++= Seq( "com.twitter" %% "finatra-http-server" % Versions.finatra, - "org.apache.httpcomponents" % "httpmime" % "4.5.13", + "org.apache.httpcomponents" % "httpmime" % "4.5.14", // Testing "com.twitter" %% "inject-server" % Versions.finatra % Test, "com.twitter" %% "inject-app" % Versions.finatra % Test, From 4777eb13418786fed2848c141ddd9a03be69a4f6 Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 5 Dec 2022 11:49:42 +0100 Subject: [PATCH 018/120] Add info about support to the docs --- doc/index.md | 4 ++++ generated-doc/out/index.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/doc/index.md b/doc/index.md index 7eee557f0e..a3628f8d8b 100644 --- a/doc/index.md +++ b/doc/index.md @@ -177,6 +177,10 @@ Development and maintenance of sttp tapir is sponsored by [SoftwareMill](https:/ [![](https://files.softwaremill.com/logo/logo.png "SoftwareMill")](https://softwaremill.com) +## Commercial Support + +We offer commercial support for sttp and related technologies, as well as development services. [Contact us](https://softwaremill.com/contact/) to learn more about our offer! + ## Table of contents ```eval_rst diff --git a/generated-doc/out/index.md b/generated-doc/out/index.md index 9529e77276..4f24382569 100644 --- a/generated-doc/out/index.md +++ b/generated-doc/out/index.md @@ -177,6 +177,10 @@ Development and maintenance of sttp tapir is sponsored by [SoftwareMill](https:/ [![](https://files.softwaremill.com/logo/logo.png "SoftwareMill")](https://softwaremill.com) +## Commercial Support + +We offer commercial support for sttp and related technologies, as well as development services. [Contact us](https://softwaremill.com/contact/) to learn more about our offer! + ## Table of contents ```eval_rst From cbda85b615d367542d3db006282b747ebe6f72e8 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Tue, 6 Dec 2022 00:16:25 +0000 Subject: [PATCH 019/120] Update zio-json to 0.4.2 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 0e07bf1e99..d494980501 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -31,7 +31,7 @@ object Versions { val zio = "2.0.4" val zioInteropCats = "3.3.0" val zioInteropReactiveStreams = "2.0.0" - val zioJson = "0.3.0" + val zioJson = "0.4.2" val playClient = "2.1.10" val playServer = "2.8.18" val tethys = "0.26.0" From 1c0c2ccdbe1513da581a386110d220be8bacf5f7 Mon Sep 17 00:00:00 2001 From: adamw Date: Tue, 6 Dec 2022 15:45:25 +0100 Subject: [PATCH 020/120] Add link about grouping security inputs in docs --- doc/endpoint/security.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/endpoint/security.md b/doc/endpoint/security.md index 7ddb59cb37..caad4d5539 100644 --- a/doc/endpoint/security.md +++ b/doc/endpoint/security.md @@ -33,10 +33,7 @@ base64-encoded username/password combination, use: `basic[UsernamePassword]`. as a string, use: `bearer[String]`. * `auth.oauth2.authorizationCode(authorizationUrl, tokenUrl, scopes, refreshUrl): EndpointInput[String]`: creates an OAuth2 authorization using authorization code - sign in using an auth service (for documentation, requires defining also -the `oauth2-redirect.html`, see [Generating OpenAPI documentation](../openapi.md)) - -Optional and multiple authentication inputs have some additional rules as to how hey map to documentation, see the -[OpenAPI](../docs/openapi.md) page for details. +the `oauth2-redirect.html`, see [Generating OpenAPI documentation](..docs/openapi.md)) ## Authentication challenges @@ -47,6 +44,11 @@ status codes when authentication is missing, the decode failure handler can [be For example, if you define `endpoint.get.securityIn("path").securityIn(auth.basic[UsernamePassword]())` then the browser will show you a password prompt. +## Grouping authentication inputs in docs + +Optional and multiple authentication inputs have some additional rules as to how hey map to documentation, see the +["Authentication inputs and security requirements"](../docs/openapi.md) section in the OpenAPI docs for details. + ## Next Read on about [streaming support](streaming.md). From b4ef96024cbff901765afa8ea6181470cfcf4f93 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Wed, 7 Dec 2022 00:12:58 +0000 Subject: [PATCH 021/120] Update zio, zio-streams, zio-test, ... to 2.0.5 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 87b9872735..6528243a57 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -28,7 +28,7 @@ object Versions { val zio1InteropCats = "3.2.9.1" val zio1Json = "0.2.0" val zio1InteropReactiveStreams = "1.3.12" - val zio = "2.0.4" + val zio = "2.0.5" val zioInteropCats = "3.3.0" val zioInteropReactiveStreams = "2.0.0" val zioJson = "0.4.2" From 6991a2015909119f1db5f6d1d2e3c8db18a7e067 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Wed, 7 Dec 2022 17:56:39 +0100 Subject: [PATCH 022/120] Updated ZIO1 interop --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index f59d0b33cf..fece7e0485 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -27,7 +27,7 @@ object Versions { val zio1 = "1.0.17" val zio1InteropCats = "3.2.9.1" val zio1Json = "0.2.0" - val zio1InteropReactiveStreams = "1.3.12" + val zio1InteropReactiveStreams = "13.0.0.1" val zio = "2.0.2" val zioInteropCats = "3.3.0" val zioInteropReactiveStreams = "2.0.0" From 7eb416d1da1bb709a83783ce87483e61b9bab08e Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Wed, 7 Dec 2022 18:04:55 +0100 Subject: [PATCH 023/120] Fix place of update --- project/Versions.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Versions.scala b/project/Versions.scala index b9b70849d5..c05589fdf9 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -25,9 +25,9 @@ object Versions { val refined = "0.10.1" val enumeratum = "1.7.2" val zio1 = "1.0.17" - val zio1InteropCats = "3.2.9.1" + val zio1InteropCats = "13.0.0.1" val zio1Json = "0.2.0" - val zio1InteropReactiveStreams = "13.0.0.1" + val zio1InteropReactiveStreams = "1.3.12" val zio = "2.0.4" val zioInteropCats = "3.3.0" val zioInteropReactiveStreams = "2.0.0" From ad07bf82abb6ea849ad3d79ff15184fb0fedc2da Mon Sep 17 00:00:00 2001 From: scala-steward Date: Fri, 9 Dec 2022 00:06:01 +0000 Subject: [PATCH 024/120] Update vertx-web to 4.3.6 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 4886c08043..fcf160557c 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -35,7 +35,7 @@ object Versions { val playClient = "2.1.10" val playServer = "2.8.18" val tethys = "0.26.0" - val vertx = "4.3.5" + val vertx = "4.3.6" val jsScalaJavaTime = "2.5.0" val nativeScalaJavaTime = "2.4.0-M3" val jwtScala = "5.0.0" From c2284d00ee3200a1d686cd14eac48b044e4b61fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Or=C5=82owski?= Date: Fri, 9 Dec 2022 09:52:46 +0100 Subject: [PATCH 025/120] Rename methods and add comment for clarity --- core/src/main/scala/sttp/tapir/Tapir.scala | 4 ++-- .../sttp/tapir/docs/openapi/EndpointToOpenAPIPaths.scala | 3 +++ .../src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala | 2 +- .../src/main/scala/sttp/tapir/json/json4s/TapirJson4s.scala | 2 +- .../scala/sttp/tapir/json/jsoniter/TapirJsonJsoniter.scala | 2 +- .../src/main/scala/sttp/tapir/json/play/TapirJsonPlay.scala | 2 +- .../src/main/scala/sttp/tapir/json/spray/TapirJsonSpray.scala | 2 +- .../main/scala/sttp/tapir/json/circe/TapirJsonTethys.scala | 2 +- .../main/scala/sttp/tapir/json/upickle/TapirJsonuPickle.scala | 2 +- .../zio/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala | 2 +- .../src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala | 2 +- 11 files changed, 14 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/sttp/tapir/Tapir.scala b/core/src/main/scala/sttp/tapir/Tapir.scala index c24c888fd4..6478963b99 100644 --- a/core/src/main/scala/sttp/tapir/Tapir.scala +++ b/core/src/main/scala/sttp/tapir/Tapir.scala @@ -28,10 +28,10 @@ trait Tapir extends TapirExtensions with TapirComputedInputs with TapirStaticCon def paths: EndpointInput.PathsCapture[List[String]] = EndpointInput.PathsCapture(Codec.idPlain(), EndpointIO.Info.empty) /** A query parameter in any format, read using the given `codec`. */ - def anyQuery[T, CF <: CodecFormat](name: String, codec: Codec[List[String], T, CF]): EndpointInput.Query[T] = + def queryAnyFormat[T, CF <: CodecFormat](name: String, codec: Codec[List[String], T, CF]): EndpointInput.Query[T] = EndpointInput.Query(name, None, codec, EndpointIO.Info.empty) def query[T: Codec[List[String], *, TextPlain]](name: String): EndpointInput.Query[T] = - anyQuery[T, TextPlain](name, implicitly) + queryAnyFormat[T, TextPlain](name, implicitly) def queryParams: EndpointInput.QueryParams[QueryParams] = EndpointInput.QueryParams(Codec.idPlain(), EndpointIO.Info.empty) def header[T: Codec[List[String], *, TextPlain]](name: String): EndpointIO.Header[T] = diff --git a/docs/openapi-docs/src/main/scala/sttp/tapir/docs/openapi/EndpointToOpenAPIPaths.scala b/docs/openapi-docs/src/main/scala/sttp/tapir/docs/openapi/EndpointToOpenAPIPaths.scala index 20230ed6f5..5730785965 100644 --- a/docs/openapi-docs/src/main/scala/sttp/tapir/docs/openapi/EndpointToOpenAPIPaths.scala +++ b/docs/openapi-docs/src/main/scala/sttp/tapir/docs/openapi/EndpointToOpenAPIPaths.scala @@ -108,7 +108,10 @@ private[openapi] class EndpointToOpenAPIPaths(schemas: Schemas, securitySchemes: EndpointInputToParameterConverter.from(header, Right(ASchema(ASchemaType.String))) private def cookieToParameter[T](cookie: EndpointInput.Cookie[T]) = EndpointInputToParameterConverter.from(cookie, schemas(cookie.codec)) private def pathCaptureToParameter[T](p: EndpointInput.PathCapture[T]) = EndpointInputToParameterConverter.from(p, schemas(p.codec)) + private def queryToParameter[T](query: EndpointInput.Query[T]) = query.codec.format match { + // use `schema` for simple plain text scenarios and `content` for complex serializations, e.g. JSON + // see https://swagger.io/docs/specification/describing-parameters/#schema-vs-content case CodecFormat.TextPlain() => EndpointInputToParameterConverter.from(query, schemas(query.codec)) case _ => EndpointInputToParameterConverter.from(query, codecToMediaType(query.codec, query.info.examples, None, Nil)) } diff --git a/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala b/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala index b1f42a4f7f..c99af83b30 100644 --- a/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala +++ b/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala @@ -18,7 +18,7 @@ trait TapirJsonCirce { ) def jsonQuery[T: Encoder: Decoder: Schema](name: String): EndpointInput.Query[T] = - anyQuery[T, CodecFormat.Json](name, implicitly) + queryAnyFormat[T, CodecFormat.Json](name, implicitly) implicit def circeCodec[T: Encoder: Decoder: Schema]: JsonCodec[T] = sttp.tapir.Codec.json[T] { s => diff --git a/json/json4s/src/main/scala/sttp/tapir/json/json4s/TapirJson4s.scala b/json/json4s/src/main/scala/sttp/tapir/json/json4s/TapirJson4s.scala index a25c905855..1a5a7b88cb 100644 --- a/json/json4s/src/main/scala/sttp/tapir/json/json4s/TapirJson4s.scala +++ b/json/json4s/src/main/scala/sttp/tapir/json/json4s/TapirJson4s.scala @@ -17,7 +17,7 @@ trait TapirJson4s { ) def jsonQuery[T: Manifest: Schema](name: String)(implicit formats: Formats, serialization: Serialization): EndpointInput.Query[T] = - anyQuery[T, CodecFormat.Json](name, implicitly) + queryAnyFormat[T, CodecFormat.Json](name, implicitly) implicit def json4sCodec[T: Manifest: Schema](implicit formats: Formats, serialization: Serialization): JsonCodec[T] = Codec.json[T] { s => diff --git a/json/jsoniter/src/main/scala/sttp/tapir/json/jsoniter/TapirJsonJsoniter.scala b/json/jsoniter/src/main/scala/sttp/tapir/json/jsoniter/TapirJsonJsoniter.scala index b0a26e5c09..72c77148aa 100644 --- a/json/jsoniter/src/main/scala/sttp/tapir/json/jsoniter/TapirJsonJsoniter.scala +++ b/json/jsoniter/src/main/scala/sttp/tapir/json/jsoniter/TapirJsonJsoniter.scala @@ -16,7 +16,7 @@ trait TapirJsonJsoniter { ) def jsonQuery[T: JsonValueCodec: Schema](name: String): EndpointInput.Query[T] = - anyQuery[T, CodecFormat.Json](name, implicitly) + queryAnyFormat[T, CodecFormat.Json](name, implicitly) implicit def jsoniterCodec[T: JsonValueCodec: Schema]: JsonCodec[T] = sttp.tapir.Codec.json { s => diff --git a/json/playjson/src/main/scala/sttp/tapir/json/play/TapirJsonPlay.scala b/json/playjson/src/main/scala/sttp/tapir/json/play/TapirJsonPlay.scala index 9a17403ccc..b27e5834b0 100644 --- a/json/playjson/src/main/scala/sttp/tapir/json/play/TapirJsonPlay.scala +++ b/json/playjson/src/main/scala/sttp/tapir/json/play/TapirJsonPlay.scala @@ -18,7 +18,7 @@ trait TapirJsonPlay { ) def jsonQuery[T: Reads: Writes: Schema](name: String): EndpointInput.Query[T] = - anyQuery[T, CodecFormat.Json](name, implicitly) + queryAnyFormat[T, CodecFormat.Json](name, implicitly) implicit def readsWritesCodec[T: Reads: Writes: Schema]: JsonCodec[T] = Codec.json[T] { s => diff --git a/json/sprayjson/src/main/scala/sttp/tapir/json/spray/TapirJsonSpray.scala b/json/sprayjson/src/main/scala/sttp/tapir/json/spray/TapirJsonSpray.scala index b5b50e52ba..c2dd9f936b 100644 --- a/json/sprayjson/src/main/scala/sttp/tapir/json/spray/TapirJsonSpray.scala +++ b/json/sprayjson/src/main/scala/sttp/tapir/json/spray/TapirJsonSpray.scala @@ -18,7 +18,7 @@ trait TapirJsonSpray { ) def jsonQuery[T: JsonFormat: Schema](name: String): EndpointInput.Query[T] = - anyQuery[T, CodecFormat.Json](name, implicitly) + queryAnyFormat[T, CodecFormat.Json](name, implicitly) implicit def jsonFormatCodec[T: JsonFormat: Schema]: JsonCodec[T] = Codec.json { s => diff --git a/json/tethys/src/main/scala/sttp/tapir/json/circe/TapirJsonTethys.scala b/json/tethys/src/main/scala/sttp/tapir/json/circe/TapirJsonTethys.scala index f7c58653f9..ba702b5c91 100644 --- a/json/tethys/src/main/scala/sttp/tapir/json/circe/TapirJsonTethys.scala +++ b/json/tethys/src/main/scala/sttp/tapir/json/circe/TapirJsonTethys.scala @@ -15,7 +15,7 @@ trait TapirJsonTethys { ) def jsonQuery[T: JsonWriter: JsonReader: Schema](name: String): EndpointInput.Query[T] = - anyQuery[T, CodecFormat.Json](name, implicitly) + queryAnyFormat[T, CodecFormat.Json](name, implicitly) implicit def tethysCodec[T: JsonReader: JsonWriter: Schema]: JsonCodec[T] = Codec.json(s => diff --git a/json/upickle/src/main/scala/sttp/tapir/json/upickle/TapirJsonuPickle.scala b/json/upickle/src/main/scala/sttp/tapir/json/upickle/TapirJsonuPickle.scala index e321d4a93c..c94fe17cd6 100644 --- a/json/upickle/src/main/scala/sttp/tapir/json/upickle/TapirJsonuPickle.scala +++ b/json/upickle/src/main/scala/sttp/tapir/json/upickle/TapirJsonuPickle.scala @@ -16,7 +16,7 @@ trait TapirJsonuPickle { ) def jsonQuery[T: ReadWriter: Schema](name: String): EndpointInput.Query[T] = - anyQuery[T, CodecFormat.Json](name, implicitly) + queryAnyFormat[T, CodecFormat.Json](name, implicitly) implicit def readWriterCodec[T: ReadWriter: Schema]: JsonCodec[T] = Codec.json[T] { s => diff --git a/json/zio/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala b/json/zio/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala index 06bece5f5d..e9ca6ec439 100644 --- a/json/zio/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala +++ b/json/zio/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala @@ -19,7 +19,7 @@ trait TapirJsonZio { ) def jsonQuery[T: JsonEncoder: JsonDecoder: Schema](name: String): EndpointInput.Query[T] = - anyQuery[T, CodecFormat.Json](name, implicitly) + queryAnyFormat[T, CodecFormat.Json](name, implicitly) implicit def zioCodec[T: JsonEncoder: JsonDecoder: Schema]: JsonCodec[T] = sttp.tapir.Codec.json[T] { s => diff --git a/json/zio1/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala b/json/zio1/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala index 06bece5f5d..e9ca6ec439 100644 --- a/json/zio1/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala +++ b/json/zio1/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala @@ -19,7 +19,7 @@ trait TapirJsonZio { ) def jsonQuery[T: JsonEncoder: JsonDecoder: Schema](name: String): EndpointInput.Query[T] = - anyQuery[T, CodecFormat.Json](name, implicitly) + queryAnyFormat[T, CodecFormat.Json](name, implicitly) implicit def zioCodec[T: JsonEncoder: JsonDecoder: Schema]: JsonCodec[T] = sttp.tapir.Codec.json[T] { s => From c9c66b3c3cfe30161732931282dc2c6748cd1b76 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sat, 10 Dec 2022 00:14:15 +0000 Subject: [PATCH 026/120] Update jsoniter-scala-core, ... to 2.19.1 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index d95d30ddcd..d915b69971 100644 --- a/build.sbt +++ b/build.sbt @@ -774,8 +774,8 @@ lazy val jsoniterScala: ProjectMatrix = (projectMatrix in file("json/jsoniter")) .settings( name := "tapir-jsoniter-scala", libraryDependencies ++= Seq( - "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.18.1", - "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.18.1" % Test, + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.19.1", + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.19.1" % Test, scalaTest.value % Test ) ) From edce475aea83a948c3b23d1485f8c0f54dc9e12f Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 12 Dec 2022 11:53:12 +0100 Subject: [PATCH 027/120] Discourse --- README.md | 6 +++--- doc/contributing.md | 2 +- generated-doc/out/contributing.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 62a4d95101..a81ff3c535 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ ![tapir](https://github.com/softwaremill/tapir/raw/master/banner.png) -# Happy 1.0 birthday, tapir! +# Welcome! -[![Join the chat at https://gitter.im/softwaremill/tapir](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/softwaremill/tapir?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Ideas, suggestions, problems, questions](https://img.shields.io/badge/Discourse-ask%20question-blue)](https://softwaremill.community/c/tapir) [![CI](https://github.com/softwaremill/tapir/workflows/CI/badge.svg)](https://github.com/softwaremill/tapir/actions?query=workflow%3A%22CI%22) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.sttp.tapir/tapir-core_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.sttp.tapir/tapir-core_2.13) @@ -174,7 +174,7 @@ All suggestions welcome :) See the list of [issues](https://github.com/softwaremill/tapir/issues) and pick one! Or report your own. If you are having doubts on the *why* or *how* something works, don't hesitate to ask a question on -[gitter](https://gitter.im/softwaremill/tapir) or via github. This probably means that the documentation, scaladocs or +[discourse](https://softwaremill.community/c/tapir) or via github. This probably means that the documentation, scaladocs or code is unclear and be improved for the benefit of all. The `core` module needs to remain binary-compatible with earlier versions. To check if your changes meet this requirement, diff --git a/doc/contributing.md b/doc/contributing.md index a3c61b94eb..e4e5421705 100644 --- a/doc/contributing.md +++ b/doc/contributing.md @@ -6,7 +6,7 @@ If you'd like to contribute, see the list of [issues](https://github.com/softwar Or report your own. If you have an idea you'd like to discuss, that's always a good option. If you are having doubts on the *why* or *how* something works, don't hesitate to ask a question on -[gitter](https://gitter.im/softwaremill/tapir) or via github. This probably means that the documentation, scaladocs or +[discourse](https://softwaremill.community/c/tapir) or via github. This probably means that the documentation, scaladocs or code is unclear and can be improved for the benefit of all. ## Acknowledgments diff --git a/generated-doc/out/contributing.md b/generated-doc/out/contributing.md index a3c61b94eb..e4e5421705 100644 --- a/generated-doc/out/contributing.md +++ b/generated-doc/out/contributing.md @@ -6,7 +6,7 @@ If you'd like to contribute, see the list of [issues](https://github.com/softwar Or report your own. If you have an idea you'd like to discuss, that's always a good option. If you are having doubts on the *why* or *how* something works, don't hesitate to ask a question on -[gitter](https://gitter.im/softwaremill/tapir) or via github. This probably means that the documentation, scaladocs or +[discourse](https://softwaremill.community/c/tapir) or via github. This probably means that the documentation, scaladocs or code is unclear and can be improved for the benefit of all. ## Acknowledgments From d09e35899b7a920e09002eed3f07dd8d9a29abe4 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Tue, 13 Dec 2022 00:16:55 +0000 Subject: [PATCH 028/120] Update netty-all to 4.1.86.Final --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index fcf160557c..76bbbfa5a6 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -53,5 +53,5 @@ object Versions { val openTelemetry = "1.20.1" val mockServer = "5.14.0" val dogstatsdClient = "4.1.0" - val nettyAll = "4.1.82.Final" + val nettyAll = "4.1.86.Final" } From 9eb04c49964c862cec18aff381ed40963a1b745d Mon Sep 17 00:00:00 2001 From: Krzysztof Pado Date: Tue, 13 Dec 2022 00:06:00 -0800 Subject: [PATCH 029/120] Build zio modules for ScalaJS --- build.sbt | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/build.sbt b/build.sbt index d915b69971..738aacd5f3 100644 --- a/build.sbt +++ b/build.sbt @@ -567,14 +567,18 @@ lazy val zio1: ProjectMatrix = (projectMatrix in file("integrations/zio1")) name := "tapir-zio1", testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"), libraryDependencies ++= Seq( - "dev.zio" %% "zio" % Versions.zio1, - "dev.zio" %% "zio-streams" % Versions.zio1, - "dev.zio" %% "zio-test" % Versions.zio1 % Test, - "dev.zio" %% "zio-test-sbt" % Versions.zio1 % Test, - "com.softwaremill.sttp.shared" %% "zio1" % Versions.sttpShared + "dev.zio" %%% "zio" % Versions.zio1, + "dev.zio" %%% "zio-streams" % Versions.zio1, + "dev.zio" %%% "zio-test" % Versions.zio1 % Test, + "dev.zio" %%% "zio-test-sbt" % Versions.zio1 % Test, + "com.softwaremill.sttp.shared" %%% "zio1" % Versions.sttpShared ) ) .jvmPlatform(scalaVersions = scala2And3Versions) + .jsPlatform( + scalaVersions = scala2And3Versions, + settings = commonJsSettings + ) .dependsOn(core, serverCore % Test) lazy val zio: ProjectMatrix = (projectMatrix in file("integrations/zio")) @@ -583,14 +587,18 @@ lazy val zio: ProjectMatrix = (projectMatrix in file("integrations/zio")) name := "tapir-zio", testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"), libraryDependencies ++= Seq( - "dev.zio" %% "zio" % Versions.zio, - "dev.zio" %% "zio-streams" % Versions.zio, - "dev.zio" %% "zio-test" % Versions.zio % Test, - "dev.zio" %% "zio-test-sbt" % Versions.zio % Test, - "com.softwaremill.sttp.shared" %% "zio" % Versions.sttpShared + "dev.zio" %%% "zio" % Versions.zio, + "dev.zio" %%% "zio-streams" % Versions.zio, + "dev.zio" %%% "zio-test" % Versions.zio % Test, + "dev.zio" %%% "zio-test-sbt" % Versions.zio % Test, + "com.softwaremill.sttp.shared" %%% "zio" % Versions.sttpShared ) ) .jvmPlatform(scalaVersions = scala2And3Versions) + .jsPlatform( + scalaVersions = scala2And3Versions, + settings = commonJsSettings + ) .dependsOn(core, serverCore % Test) lazy val derevo: ProjectMatrix = (projectMatrix in file("integrations/derevo")) From 690440b9619ad5ac3859fd1a33b8f54a51a6d760 Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 14 Dec 2022 14:47:09 +0100 Subject: [PATCH 030/120] Add an attribute to customise default decode failure handling for individual inputs/outputs --- doc/server/errors.md | 15 ++++- .../decodefailure/DecodeFailureHandler.scala | 56 ++++++++++++++----- .../tapir/server/tests/ServerBasicTests.scala | 39 ++++++++++++- 3 files changed, 92 insertions(+), 18 deletions(-) diff --git a/doc/server/errors.md b/doc/server/errors.md index 181b751ab8..7da66c7d2c 100644 --- a/doc/server/errors.md +++ b/doc/server/errors.md @@ -114,7 +114,20 @@ swapped, e.g. to return responses in a different format (other than plain text), The default decode failure handler also has the option to return a `400 Bad Request`, instead of a no-match (ultimately leading to a `404 Not Found`), when the "shape" of the path matches (that is, the number of segments in the request and endpoint's paths are the same), but when decoding some part of the path ends in an error. See the -`badRequestOnPathErrorIfPathShapeMatches` in `ServerDefaults`. +scaladoc for `DefaultDecodeFailureHandler.default` and the `badRequestOnPathErrorIfPathShapeMatches` and +`badRequestOnPathInvalidIfPathShapeMatches` parameters of `DefaultDecodeFailureHandler.response`. + +When using the `DefaultDecodeFailureHandler`, decode failure handling can be overriden on a per-input/output basis, +by setting an attribute. For example: + +```scala mdoc:compile-only +import sttp.tapir._ +// bringing into scope the onDecodeFailureBadRequest extension method +import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler.OnDecodeFailure._ + +// be default, when the customer_id is not an int, the next endpoint would be tried; here, we always return a bad request +endpoint.in("customer" / path[Int]("customer_id").onDecodeFailureBadRequest) +``` ## Customising how error messages are rendered diff --git a/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala b/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala index c20c201a61..0abe688ce7 100644 --- a/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala +++ b/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala @@ -61,7 +61,21 @@ object DefaultDecodeFailureHandler { * The error messages contain information about the source of the decode error, and optionally the validation error detail that caused * the failure. * + * The default decode failure handler can be customised by providing alternate functions for deciding whether a response should be sent, + * creating the error message and creating the response. + * + * Furthermore, how decode failures are handled can be adjusted globally by changing the flags passed to [[respond]]. By default, if the + * shape of the path for an endpoint matches the request, but decoding a path capture causes an error (e.g. a `path[Int]("amount")` + * cannot be parsed), the next endpoint is tried. However, if there's a validation error (e.g. a `path[Kind]("kind")`, where `Kind` is an + * enum, and a value outside the enumeration values is provided), a 400 response is sent. + * + * Finally, behavior can be adjusted per-endpoint-input, by setting an attribute. Import the [[OnDecodeFailure]] object and use the + * [[OnDecodeFailure.RichEndpointTransput.onDecodeFailureBadRequest]] and + * [[OnDecodeFailure.RichEndpointTransput.onDecodeFailureNextEndpoint]] extension methods. + * * This is only used for failures that occur when decoding inputs, not for exceptions that happen when the server logic is invoked. + * Exceptions can be either handled by the server logic, and converted to an error output value. Uncaught exceptions can be handled using + * the [[sttp.tapir.server.interceptor.exception.ExceptionInterceptor]]. */ val default: DefaultDecodeFailureHandler = DefaultDecodeFailureHandler( respond(_, badRequestOnPathErrorIfPathShapeMatches = false, badRequestOnPathInvalidIfPathShapeMatches = true), @@ -95,35 +109,38 @@ object DefaultDecodeFailureHandler { badRequestOnPathInvalidIfPathShapeMatches: Boolean ): Option[(StatusCode, List[Header])] = { failingInput(ctx) match { - case _: EndpointInput.Query[_] => Some(onlyStatus(StatusCode.BadRequest)) - case _: EndpointInput.QueryParams[_] => Some(onlyStatus(StatusCode.BadRequest)) - case _: EndpointInput.Cookie[_] => Some(onlyStatus(StatusCode.BadRequest)) + case i: EndpointTransput.Atom[_] if i.attribute(OnDecodeFailure.key).contains(OnDecodeFailure(true)) => respondBadRequest + case i: EndpointTransput.Atom[_] if i.attribute(OnDecodeFailure.key).contains(OnDecodeFailure(false)) => None + case _: EndpointInput.Query[_] => respondBadRequest + case _: EndpointInput.QueryParams[_] => respondBadRequest + case _: EndpointInput.Cookie[_] => respondBadRequest case h: EndpointIO.Header[_] if ctx.failure.isInstanceOf[DecodeResult.Mismatch] && h.name == HeaderNames.ContentType => - Some(onlyStatus(StatusCode.UnsupportedMediaType)) - case _: EndpointIO.Header[_] => Some(onlyStatus(StatusCode.BadRequest)) + respondUnsupportedMediaType + case _: EndpointIO.Header[_] => respondBadRequest case fh: EndpointIO.FixedHeader[_] if ctx.failure.isInstanceOf[DecodeResult.Mismatch] && fh.h.name == HeaderNames.ContentType => - Some(onlyStatus(StatusCode.UnsupportedMediaType)) - case _: EndpointIO.FixedHeader[_] => Some(onlyStatus(StatusCode.BadRequest)) - case _: EndpointIO.Headers[_] => Some(onlyStatus(StatusCode.BadRequest)) - case _: EndpointIO.Body[_, _] => Some(onlyStatus(StatusCode.BadRequest)) - case _: EndpointIO.OneOfBody[_, _] if ctx.failure.isInstanceOf[DecodeResult.Mismatch] => - Some(onlyStatus(StatusCode.UnsupportedMediaType)) - case _: EndpointIO.StreamBodyWrapper[_, _] => Some(onlyStatus(StatusCode.BadRequest)) + respondUnsupportedMediaType + case _: EndpointIO.FixedHeader[_] => respondBadRequest + case _: EndpointIO.Headers[_] => respondBadRequest + case _: EndpointIO.Body[_, _] => respondBadRequest + case _: EndpointIO.OneOfBody[_, _] if ctx.failure.isInstanceOf[DecodeResult.Mismatch] => respondUnsupportedMediaType + case _: EndpointIO.StreamBodyWrapper[_, _] => respondBadRequest // we assume that the only decode failure that might happen during path segment decoding is an error // a non-standard path decoder might return Missing/Multiple/Mismatch, but that would be indistinguishable from // a path shape mismatch case _: EndpointInput.PathCapture[_] if (badRequestOnPathErrorIfPathShapeMatches && ctx.failure.isInstanceOf[DecodeResult.Error]) || (badRequestOnPathInvalidIfPathShapeMatches && ctx.failure.isInstanceOf[DecodeResult.InvalidValue]) => - Some(onlyStatus(StatusCode.BadRequest)) + respondBadRequest // if the failing input contains an authentication input (potentially nested), sending its challenge case FirstAuth(a) => Some((StatusCode.Unauthorized, Header.wwwAuthenticate(a.challenge))) // other basic endpoints - the request doesn't match, but not returning a response (trying other endpoints) case _: EndpointInput.Basic[_] => None // all other inputs (tuples, mapped) - responding with bad request - case _ => Some(onlyStatus(StatusCode.BadRequest)) + case _ => respondBadRequest } } + private val respondBadRequest = Some(onlyStatus(StatusCode.BadRequest)) + private val respondUnsupportedMediaType = Some(onlyStatus(StatusCode.UnsupportedMediaType)) def respondNotFoundIfHasAuth( ctx: DecodeFailureContext, @@ -285,4 +302,15 @@ object DefaultDecodeFailureHandler { case _ => v } } + + private[decodefailure] case class OnDecodeFailure(value: Boolean) extends AnyVal + + object OnDecodeFailure { + private[decodefailure] val key: AttributeKey[OnDecodeFailure] = AttributeKey[OnDecodeFailure] + + implicit class RichEndpointTransput[ET <: EndpointTransput.Atom[_]](val et: ET) extends AnyVal { + def onDecodeFailureBadRequest: ET = et.attribute(key, OnDecodeFailure(true)).asInstanceOf[ET] + def onDecodeFailureNextEndpoint: ET = et.attribute(key, OnDecodeFailure(false)).asInstanceOf[ET] + } + } } diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala index be802755f9..e2e1401609 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala @@ -40,7 +40,7 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( methodMatchingTests() ++ pathMatchingTests() ++ pathMatchingMultipleEndpoints() ++ - pathShapeMatchingTests() ++ + customiseDecodeFailureHandlerTests() ++ serverSecurityLogicTests() ++ (if (inputStreamSupport) inputStreamTests() else Nil) ++ exceptionTests() @@ -593,10 +593,10 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( } ) - def pathShapeMatchingTests(): List[Test] = List( + def customiseDecodeFailureHandlerTests(): List[Test] = List( testServer( in_path_fixed_capture_fixed_capture, - "Returns 400 if path 'shape' matches, but failed to parse a path parameter", + "Returns 400 if path 'shape' matches, but failed to parse a path parameter, using a custom decode failure handler", _.decodeFailureHandler(decodeFailureHandlerBadRequestOnPathFailure) )(_ => pureResult(Either.right[Unit, Unit](()))) { (backend, baseUri) => basicRequest.get(uri"$baseUri/customer/asd/orders/2").send(backend).map { response => @@ -615,6 +615,39 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( .get(uri"$baseUri/customer/asd/orders/2/xyz") .send(backend) .map(response => response.code shouldBe StatusCode.NotFound) + }, { + import DefaultDecodeFailureHandler.OnDecodeFailure._ + testServer( + endpoint.get.in("customer" / path[Int]("customer_id").onDecodeFailureBadRequest), + "Returns 400 if path 'shape' matches, but failed to parse a path parameter, using .badRequestOnDecodeFailure" + )(_ => pureResult(Either.right[Unit, Unit](()))) { (backend, baseUri) => + basicRequest.get(uri"$baseUri/customer/asd").send(backend).map { response => + response.body shouldBe Left("Invalid value for: path parameter customer_id") + response.code shouldBe StatusCode.BadRequest + } + } + }, { + import DefaultDecodeFailureHandler.OnDecodeFailure._ + testServer( + "Tries next endpoint if path 'shape' matches, but validation fails, using .badRequestOnDecodeFailure", + NonEmptyList.of( + route( + endpoint.get + .in("customer" / path[Int]("customer_id").validate(Validator.min(10)).onDecodeFailureNextEndpoint) + .out(stringBody) + .serverLogic((_: Int) => pureResult("e1".asRight[Unit])) + ), + route( + endpoint.get + .in("customer" / path[String]("customer_id")) + .out(stringBody) + .serverLogic((_: String) => pureResult("e2".asRight[Unit])) + ) + ) + ) { (backend, baseUri) => + basicStringRequest.get(uri"$baseUri/customer/20").send(backend).map(_.body shouldBe "e1") >> + basicStringRequest.get(uri"$baseUri/customer/2").send(backend).map(_.body shouldBe "e2") + } } ) From 5908388270fc8c93de63be018eae77e3da7f8cdf Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 14 Dec 2022 14:52:35 +0100 Subject: [PATCH 031/120] More docs --- doc/server/errors.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/doc/server/errors.md b/doc/server/errors.md index 7da66c7d2c..7a0525623a 100644 --- a/doc/server/errors.md +++ b/doc/server/errors.md @@ -115,7 +115,27 @@ The default decode failure handler also has the option to return a `400 Bad Requ leading to a `404 Not Found`), when the "shape" of the path matches (that is, the number of segments in the request and endpoint's paths are the same), but when decoding some part of the path ends in an error. See the scaladoc for `DefaultDecodeFailureHandler.default` and the `badRequestOnPathErrorIfPathShapeMatches` and -`badRequestOnPathInvalidIfPathShapeMatches` parameters of `DefaultDecodeFailureHandler.response`. +`badRequestOnPathInvalidIfPathShapeMatches` parameters of `DefaultDecodeFailureHandler.response`. For example: + +```scala mdoc:compile-only +import sttp.tapir._ +import sttp.tapir.server.akkahttp.AkkaHttpServerOptions +import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler +import scala.concurrent.ExecutionContext.Implicits.global + +val myDecodeFailureHandler = DefaultDecodeFailureHandler.default.copy( + respond = DefaultDecodeFailureHandler.respond( + _, + badRequestOnPathErrorIfPathShapeMatches = true, + badRequestOnPathInvalidIfPathShapeMatches = true + ) +) + +val myServerOptions: AkkaHttpServerOptions = AkkaHttpServerOptions + .customiseInterceptors + .decodeFailureHandler(myDecodeFailureHandler) + .options +``` When using the `DefaultDecodeFailureHandler`, decode failure handling can be overriden on a per-input/output basis, by setting an attribute. For example: From 5df149d22bfa332c5b42f53d721b4989d4a9c8c7 Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 14 Dec 2022 14:53:45 +0100 Subject: [PATCH 032/120] More docs --- doc/server/errors.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/server/errors.md b/doc/server/errors.md index 7a0525623a..6a7c57b3aa 100644 --- a/doc/server/errors.md +++ b/doc/server/errors.md @@ -112,10 +112,9 @@ an error or return a "no match", create error messages and create the response. swapped, e.g. to return responses in a different format (other than plain text), or customise the error messages. The default decode failure handler also has the option to return a `400 Bad Request`, instead of a no-match (ultimately -leading to a `404 Not Found`), when the "shape" of the path matches (that is, the number of segments in the request -and endpoint's paths are the same), but when decoding some part of the path ends in an error. See the -scaladoc for `DefaultDecodeFailureHandler.default` and the `badRequestOnPathErrorIfPathShapeMatches` and -`badRequestOnPathInvalidIfPathShapeMatches` parameters of `DefaultDecodeFailureHandler.response`. For example: +leading to a `404 Not Found`), when the "shape" of the path matches (that is, the constant parts and number of segments +in the request and endpoint's paths are the same), but when decoding some part of the path ends in an error. See the +scaladoc for `DefaultDecodeFailureHandler.default` and parameters of `DefaultDecodeFailureHandler.response`. For example: ```scala mdoc:compile-only import sttp.tapir._ @@ -137,8 +136,8 @@ val myServerOptions: AkkaHttpServerOptions = AkkaHttpServerOptions .options ``` -When using the `DefaultDecodeFailureHandler`, decode failure handling can be overriden on a per-input/output basis, -by setting an attribute. For example: +Moreover, when using the `DefaultDecodeFailureHandler`, decode failure handling can be overriden on a per-input/output +basis, by setting an attribute. For example: ```scala mdoc:compile-only import sttp.tapir._ From f1eaebd36319bfd2d4e7510adaf393bf7b3c722e Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 14 Dec 2022 15:14:46 +0100 Subject: [PATCH 033/120] Fix for 2.12 --- .../decodefailure/DecodeFailureHandler.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala b/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala index 0abe688ce7..c118e5c3ba 100644 --- a/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala +++ b/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala @@ -109,11 +109,11 @@ object DefaultDecodeFailureHandler { badRequestOnPathInvalidIfPathShapeMatches: Boolean ): Option[(StatusCode, List[Header])] = { failingInput(ctx) match { - case i: EndpointTransput.Atom[_] if i.attribute(OnDecodeFailure.key).contains(OnDecodeFailure(true)) => respondBadRequest - case i: EndpointTransput.Atom[_] if i.attribute(OnDecodeFailure.key).contains(OnDecodeFailure(false)) => None - case _: EndpointInput.Query[_] => respondBadRequest - case _: EndpointInput.QueryParams[_] => respondBadRequest - case _: EndpointInput.Cookie[_] => respondBadRequest + case i: EndpointTransput.Atom[_] if i.attribute(OnDecodeFailure.key).contains(OnDecodeFailureAttribute(true)) => respondBadRequest + case i: EndpointTransput.Atom[_] if i.attribute(OnDecodeFailure.key).contains(OnDecodeFailureAttribute(false)) => None + case _: EndpointInput.Query[_] => respondBadRequest + case _: EndpointInput.QueryParams[_] => respondBadRequest + case _: EndpointInput.Cookie[_] => respondBadRequest case h: EndpointIO.Header[_] if ctx.failure.isInstanceOf[DecodeResult.Mismatch] && h.name == HeaderNames.ContentType => respondUnsupportedMediaType case _: EndpointIO.Header[_] => respondBadRequest @@ -303,14 +303,14 @@ object DefaultDecodeFailureHandler { } } - private[decodefailure] case class OnDecodeFailure(value: Boolean) extends AnyVal + private[decodefailure] case class OnDecodeFailureAttribute(value: Boolean) extends AnyVal object OnDecodeFailure { - private[decodefailure] val key: AttributeKey[OnDecodeFailure] = AttributeKey[OnDecodeFailure] + private[decodefailure] val key: AttributeKey[OnDecodeFailureAttribute] = AttributeKey[OnDecodeFailureAttribute] implicit class RichEndpointTransput[ET <: EndpointTransput.Atom[_]](val et: ET) extends AnyVal { - def onDecodeFailureBadRequest: ET = et.attribute(key, OnDecodeFailure(true)).asInstanceOf[ET] - def onDecodeFailureNextEndpoint: ET = et.attribute(key, OnDecodeFailure(false)).asInstanceOf[ET] + def onDecodeFailureBadRequest: ET = et.attribute(key, OnDecodeFailureAttribute(true)).asInstanceOf[ET] + def onDecodeFailureNextEndpoint: ET = et.attribute(key, OnDecodeFailureAttribute(false)).asInstanceOf[ET] } } } From 8608a45010b723aa4ce32510e6143521a194c2f5 Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 14 Dec 2022 16:08:50 +0100 Subject: [PATCH 034/120] Fix test for Armeria --- .../tapir/server/tests/ServerBasicTests.scala | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala index e2e1401609..426b7293ef 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala @@ -629,19 +629,19 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( }, { import DefaultDecodeFailureHandler.OnDecodeFailure._ testServer( - "Tries next endpoint if path 'shape' matches, but validation fails, using .badRequestOnDecodeFailure", + "Tries next endpoint if path 'shape' matches, but validation fails, using .onDecodeFailureNextEndpoint", NonEmptyList.of( route( - endpoint.get - .in("customer" / path[Int]("customer_id").validate(Validator.min(10)).onDecodeFailureNextEndpoint) - .out(stringBody) - .serverLogic((_: Int) => pureResult("e1".asRight[Unit])) - ), - route( - endpoint.get - .in("customer" / path[String]("customer_id")) - .out(stringBody) - .serverLogic((_: String) => pureResult("e2".asRight[Unit])) + List( + endpoint.get + .in("customer" / path[Int]("customer_id").validate(Validator.min(10)).onDecodeFailureNextEndpoint) + .out(stringBody) + .serverLogic((_: Int) => pureResult("e1".asRight[Unit])), + endpoint.get + .in("customer" / path[String]("customer_id")) + .out(stringBody) + .serverLogic((_: String) => pureResult("e2".asRight[Unit])) + ) ) ) ) { (backend, baseUri) => From 229426648c269953400d7b6fd5e364af8a141820 Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 14 Dec 2022 21:05:49 +0100 Subject: [PATCH 035/120] Typo --- doc/server/errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/server/errors.md b/doc/server/errors.md index 6a7c57b3aa..8d947ffb4a 100644 --- a/doc/server/errors.md +++ b/doc/server/errors.md @@ -144,7 +144,7 @@ import sttp.tapir._ // bringing into scope the onDecodeFailureBadRequest extension method import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler.OnDecodeFailure._ -// be default, when the customer_id is not an int, the next endpoint would be tried; here, we always return a bad request +// by default, when the customer_id is not an int, the next endpoint would be tried; here, we always return a bad request endpoint.in("customer" / path[Int]("customer_id").onDecodeFailureBadRequest) ``` From 38cacebbf43691c6f8fe2aef10d3b65083cbf373 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Thu, 15 Dec 2022 00:05:58 +0000 Subject: [PATCH 036/120] Update sbt-softwaremill-browser-test-js, ... to 2.0.12 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 3015f40725..ce04cbd32a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -val sbtSoftwareMillVersion = "2.0.9" +val sbtSoftwareMillVersion = "2.0.12" addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % sbtSoftwareMillVersion) addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % sbtSoftwareMillVersion) addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-browser-test-js" % sbtSoftwareMillVersion) From cbbb0a3390d2dfef8a06ac0f02749550933e8488 Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 15 Dec 2022 08:32:20 +0100 Subject: [PATCH 037/120] Fix for scala 3 --- .../main/scala/sttp/tapir/server/tests/ServerBasicTests.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala index 426b7293ef..90dbe77a4c 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala @@ -636,11 +636,11 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( endpoint.get .in("customer" / path[Int]("customer_id").validate(Validator.min(10)).onDecodeFailureNextEndpoint) .out(stringBody) - .serverLogic((_: Int) => pureResult("e1".asRight[Unit])), + .serverLogic[F]((_: Int) => pureResult("e1".asRight[Unit])), endpoint.get .in("customer" / path[String]("customer_id")) .out(stringBody) - .serverLogic((_: String) => pureResult("e2".asRight[Unit])) + .serverLogic[F]((_: String) => pureResult("e2".asRight[Unit])) ) ) ) From 68e643fcc2a40c9e6cb2b689d756849aab1dc6c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Or=C5=82owski?= Date: Fri, 16 Dec 2022 13:00:19 +0100 Subject: [PATCH 038/120] Add JSON query parameter explanation in the docs --- doc/endpoint/json.md | 19 +++++++++++++++++-- .../tapir/docs/openapi/VerifyYamlTest.scala | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/doc/endpoint/json.md b/doc/endpoint/json.md index 9afbed380a..9d6e10e181 100644 --- a/doc/endpoint/json.md +++ b/doc/endpoint/json.md @@ -3,7 +3,7 @@ Json values are supported through codecs, which encode/decode values to json strings. Most often, you'll be using a third-party library to perform the actual json parsing/printing. See below for the list of supported libraries. -All the integrations, when imported into scope, define a `jsonBody[T]` method. +All the integrations, when imported into scope, define `jsonBody[T]` and `jsonQuery[T]` methods. Instead of providing the json codec as an implicit value, this method depends on library-specific implicits being in scope, and basing on these values creates a json codec. The derivation also requires @@ -272,7 +272,22 @@ have different defaults when it comes to a discrimination strategy, so in order documentation) in sync with how the values are serialised, you will have to configure schema derivation as well. Schemas are referenced at the point of `jsonBody` usage, so any configuration must be available in the implicit scope -when this method is called. +when these methods are called. + +## JSON query parameters + +You can specify query parameters in JSON format by using the `jsonQuery` method. For example, using Circe: + +```scala mdoc:compile-only +import sttp.tapir._ +import sttp.tapir.json.circe._ +import sttp.tapir.generic.auto._ +import io.circe.generic.auto._ + +case class Book(author: String, title: String, year: Int) + +val bookQuery: EndpointInput.Query[Book] = jsonQuery[Book]("book") +``` ## Next diff --git a/docs/openapi-docs/src/test/scalajvm/sttp/tapir/docs/openapi/VerifyYamlTest.scala b/docs/openapi-docs/src/test/scalajvm/sttp/tapir/docs/openapi/VerifyYamlTest.scala index d5c52259e6..52bbdd0391 100644 --- a/docs/openapi-docs/src/test/scalajvm/sttp/tapir/docs/openapi/VerifyYamlTest.scala +++ b/docs/openapi-docs/src/test/scalajvm/sttp/tapir/docs/openapi/VerifyYamlTest.scala @@ -787,7 +787,7 @@ class VerifyYamlTest extends AnyFunSuite with Matchers { val codec = Codec.listHead(Codec.json[String](DecodeResult.Value(_))(identity)) val actualYaml = OpenAPIDocsInterpreter() .toOpenAPI( - endpoint.post.in(anyQuery[String, CodecFormat.Json]("name", codec).example("alan").default("tom")), + endpoint.post.in(queryAnyFormat[String, CodecFormat.Json]("name", codec).example("alan").default("tom")), Info("Entities", "1.0") ) .toYaml From 81b37977b6ad51292a39bcaa084e7477780a1ec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Or=C5=82owski?= Date: Fri, 16 Dec 2022 13:05:21 +0100 Subject: [PATCH 039/120] Add JSON query parameter explanation in the docs --- doc/endpoint/json.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/doc/endpoint/json.md b/doc/endpoint/json.md index 9d6e10e181..58a506b349 100644 --- a/doc/endpoint/json.md +++ b/doc/endpoint/json.md @@ -258,22 +258,6 @@ import sttp.tapir.json.zio._ Zio JSON requires `JsonEncoder` and `JsonDecoder` implicit values in scope for each type you want to serialize. -## Other JSON libraries - -To add support for additional JSON libraries, see the -[sources](https://github.com/softwaremill/tapir/blob/master/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala) -for the Circe codec (which is just a couple of lines of code). - -## Coproducts (enums, sealed traits, classes) - -If you are serialising a sealed hierarchy, such as a Scala 3 `enum`, a `sealed trait` or `sealed class`, the configuration -of [schema derivation](schemas.md) will have to match the configuration of your json library. Different json libraries -have different defaults when it comes to a discrimination strategy, so in order to have the schemas (and hence the -documentation) in sync with how the values are serialised, you will have to configure schema derivation as well. - -Schemas are referenced at the point of `jsonBody` usage, so any configuration must be available in the implicit scope -when these methods are called. - ## JSON query parameters You can specify query parameters in JSON format by using the `jsonQuery` method. For example, using Circe: @@ -289,6 +273,22 @@ case class Book(author: String, title: String, year: Int) val bookQuery: EndpointInput.Query[Book] = jsonQuery[Book]("book") ``` +## Other JSON libraries + +To add support for additional JSON libraries, see the +[sources](https://github.com/softwaremill/tapir/blob/master/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala) +for the Circe codec (which is just a couple of lines of code). + +## Coproducts (enums, sealed traits, classes) + +If you are serialising a sealed hierarchy, such as a Scala 3 `enum`, a `sealed trait` or `sealed class`, the configuration +of [schema derivation](schemas.md) will have to match the configuration of your json library. Different json libraries +have different defaults when it comes to a discrimination strategy, so in order to have the schemas (and hence the +documentation) in sync with how the values are serialised, you will have to configure schema derivation as well. + +Schemas are referenced at the point of `jsonBody` and `jsonQuery` usage, so any configuration must be available in the implicit scope +when these methods are called. + ## Next Read on about [working with forms](forms.md). From f1f3c5f3e995527b1f0bbbf05a33a8f4c51a81a9 Mon Sep 17 00:00:00 2001 From: adamw Date: Fri, 16 Dec 2022 20:41:53 +0100 Subject: [PATCH 040/120] Release 1.2.4 --- README.md | 2 +- generated-doc/out/client/http4s.md | 2 +- generated-doc/out/client/play.md | 2 +- generated-doc/out/client/sttp.md | 4 +- generated-doc/out/docs/asyncapi.md | 2 +- generated-doc/out/docs/openapi.md | 12 +++--- generated-doc/out/endpoint/integrations.md | 14 +++---- generated-doc/out/endpoint/json.md | 37 ++++++++++++------ generated-doc/out/endpoint/security.md | 10 +++-- .../out/generator/sbt-openapi-codegen.md | 2 +- generated-doc/out/index.md | 2 +- generated-doc/out/quickstart.md | 2 +- generated-doc/out/server/akkahttp.md | 4 +- generated-doc/out/server/armeria.md | 8 ++-- generated-doc/out/server/aws.md | 6 +-- generated-doc/out/server/errors.md | 38 +++++++++++++++++-- generated-doc/out/server/finatra.md | 4 +- generated-doc/out/server/http4s.md | 2 +- generated-doc/out/server/netty.md | 11 +++++- generated-doc/out/server/observability.md | 8 ++-- generated-doc/out/server/play.md | 2 +- generated-doc/out/server/vertx.md | 8 ++-- generated-doc/out/server/zio-http4s.md | 6 +-- generated-doc/out/server/ziohttp.md | 4 +- generated-doc/out/testing.md | 8 ++-- 25 files changed, 128 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index a81ff3c535..38a775929e 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ tapir documentation is available at [tapir.softwaremill.com](http://tapir.softwa Add the following dependency: ```sbt -"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.4" ``` Then, import: diff --git a/generated-doc/out/client/http4s.md b/generated-doc/out/client/http4s.md index 39ae321b5c..ef213ac342 100644 --- a/generated-doc/out/client/http4s.md +++ b/generated-doc/out/client/http4s.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-http4s-client" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-client" % "1.2.4" ``` To interpret an endpoint definition as an `org.http4s.Request[F]`, import: diff --git a/generated-doc/out/client/play.md b/generated-doc/out/client/play.md index 40322fbd0e..39e55314a2 100644 --- a/generated-doc/out/client/play.md +++ b/generated-doc/out/client/play.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-play-client" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-play-client" % "1.2.4" ``` To make requests using an endpoint definition using the [play client](https://github.com/playframework/play-ws), import: diff --git a/generated-doc/out/client/sttp.md b/generated-doc/out/client/sttp.md index 7734e00aaa..bace01a4a9 100644 --- a/generated-doc/out/client/sttp.md +++ b/generated-doc/out/client/sttp.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % "1.2.4" ``` To make requests using an endpoint definition using the [sttp client](https://github.com/softwaremill/sttp), import: @@ -102,7 +102,7 @@ In this case add the following dependencies (note the [`%%%`](https://www.scala- instead of the usual `%%`): ```scala -"com.softwaremill.sttp.tapir" %%% "tapir-sttp-client" % "1.2.3" +"com.softwaremill.sttp.tapir" %%% "tapir-sttp-client" % "1.2.4" "io.github.cquiroz" %%% "scala-java-time" % "2.2.0" // implementations of java.time classes for Scala.JS ``` diff --git a/generated-doc/out/docs/asyncapi.md b/generated-doc/out/docs/asyncapi.md index 720502034c..4917beb966 100644 --- a/generated-doc/out/docs/asyncapi.md +++ b/generated-doc/out/docs/asyncapi.md @@ -3,7 +3,7 @@ To use, add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-asyncapi-docs" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-asyncapi-docs" % "1.2.4" "com.softwaremill.sttp.apispec" %% "asyncapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` diff --git a/generated-doc/out/docs/openapi.md b/generated-doc/out/docs/openapi.md index 104ce47495..2c346f2a46 100644 --- a/generated-doc/out/docs/openapi.md +++ b/generated-doc/out/docs/openapi.md @@ -13,7 +13,7 @@ these steps can be done separately, giving you complete control over the process To generate OpenAPI documentation and expose it using the Swagger UI in a single step, first add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % "1.2.4" ``` Then, you can interpret a list of endpoints using `SwaggerInterpreter`. The result will be a list of file-serving @@ -55,7 +55,7 @@ for details. Similarly as above, you'll need the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-redoc-bundle" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-redoc-bundle" % "1.2.4" ``` And the server endpoints can be generated using the `sttp.tapir.redoc.bundle.RedocInterpreter` class. @@ -65,7 +65,7 @@ And the server endpoints can be generated using the `sttp.tapir.redoc.bundle.Red To generate the docs in the OpenAPI yaml format, add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.4" "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` @@ -133,7 +133,7 @@ For example, generating the OpenAPI 3.1.0 YAML string can be achieved by perform Firstly add dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.4" "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` @@ -164,12 +164,12 @@ The modules `tapir-swagger-ui` and `tapir-redoc` contain server endpoint definit yaml format, will expose it using the given context path. To use, add as a dependency either `tapir-swagger-ui`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui" % "1.2.4" ``` or `tapir-redoc`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-redoc" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-redoc" % "1.2.4" ``` Then, you'll need to pass the server endpoints to your server interpreter. For example, using akka-http: diff --git a/generated-doc/out/endpoint/integrations.md b/generated-doc/out/endpoint/integrations.md index e0ec6f9528..4b035fcd82 100644 --- a/generated-doc/out/endpoint/integrations.md +++ b/generated-doc/out/endpoint/integrations.md @@ -14,7 +14,7 @@ The `tapir-cats` module contains additional instances for some [cats](https://ty datatypes as well as additional syntax: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-cats" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-cats" % "1.2.4" ``` - `import sttp.tapir.integ.cats.codec._` - brings schema, validator and codec instances @@ -26,7 +26,7 @@ If you use [refined](https://github.com/fthomas/refined), the `tapir-refined` mo validators for `T Refined P` as long as a codec for `T` already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-refined" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-refined" % "1.2.4" ``` You'll need to extend the `sttp.tapir.codec.refined.TapirCodecRefined` @@ -47,7 +47,7 @@ The `tapir-enumeratum` module provides schemas, validators and codecs for [Enume enumerations. To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % "1.2.4" ``` Then, `import sttp.tapir.codec.enumeratum._`, or extends the `sttp.tapir.codec.enumeratum.TapirCodecEnumeratum` trait. @@ -60,7 +60,7 @@ If you use [scala-newtype](https://github.com/estatico/scala-newtype), the `tapi schemas for types with a `@newtype` and `@newsubtype` annotations as long as a codec and schema for its underlying value already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-newtype" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-newtype" % "1.2.4" ``` Then, `import sttp.tapir.codec.newtype._`, or extend the `sttp.tapir.codec.newtype.TapirCodecNewType` trait to bring the implicit values into scope. @@ -71,7 +71,7 @@ If you use [monix newtypes](https://github.com/monix/newtypes), the `tapir-monix schemas for types which extend `NewtypeWrapped` and `NewsubtypeWrapped` annotations as long as a codec and schema for its underlying value already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-monix-newtype" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-monix-newtype" % "1.2.4" ``` Then, `import sttp.tapir.codec.monix.newtype._`, or extend the `sttp.tapir.codec.monix.newtype.TapirCodecMonixNewType` trait to bring the implicit values into scope. @@ -82,7 +82,7 @@ If you use [ZIO Prelude Newtypes](https://zio.github.io/zio-prelude/docs/newtype schemas for types defined using `Newtype` and `Subtype` as long as a codec and a schema for the underlying type already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio-prelude" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-zio-prelude" % "1.2.4" ``` Then, mix in `sttp.tapir.codec.zio.prelude.newtype.TapirNewtypeSupport` into your newtype to bring the implicit values into scope: @@ -121,7 +121,7 @@ For details refer to [derevo documentation](https://github.com/tofu-tf/derevo#in To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-derevo" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-derevo" % "1.2.4" ``` Then you can derive schema for your ADT along with other typeclasses besides ADT declaration itself: diff --git a/generated-doc/out/endpoint/json.md b/generated-doc/out/endpoint/json.md index bcd2ec5cb1..7820f44753 100644 --- a/generated-doc/out/endpoint/json.md +++ b/generated-doc/out/endpoint/json.md @@ -3,7 +3,7 @@ Json values are supported through codecs, which encode/decode values to json strings. Most often, you'll be using a third-party library to perform the actual json parsing/printing. See below for the list of supported libraries. -All the integrations, when imported into scope, define a `jsonBody[T]` method. +All the integrations, when imported into scope, define `jsonBody[T]` and `jsonQuery[T]` methods. Instead of providing the json codec as an implicit value, this method depends on library-specific implicits being in scope, and basing on these values creates a json codec. The derivation also requires @@ -45,7 +45,7 @@ stringJsonBody.schema(implicitly[Schema[MyBody]].as[String]) To use [Circe](https://github.com/circe/circe), add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.2.4" ``` Next, import the package (or extend the `TapirJsonCirce` trait, see [MyTapir](../mytapir.md)): @@ -118,7 +118,7 @@ Now the above JSON object will render as To use [µPickle](http://www.lihaoyi.com/upickle/) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-upickle" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-json-upickle" % "1.2.4" ``` Next, import the package (or extend the `TapirJsonuPickle` trait, see [MyTapir](../mytapir.md) and add `TapirJsonuPickle` not `TapirCirceJson`): @@ -153,7 +153,7 @@ For more examples, including making a custom encoder/decoder, see [TapirJsonuPic To use [Play JSON](https://github.com/playframework/play-json) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-play" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-json-play" % "1.2.4" ``` Next, import the package (or extend the `TapirJsonPlay` trait, see [MyTapir](../mytapir.md) and add `TapirJsonPlay` not `TapirCirceJson`): @@ -169,7 +169,7 @@ Play JSON requires `Reads` and `Writes` implicit values in scope for each type y To use [Spray JSON](https://github.com/spray/spray-json) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-spray" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-json-spray" % "1.2.4" ``` Next, import the package (or extend the `TapirJsonSpray` trait, see [MyTapir](../mytapir.md) and add `TapirJsonSpray` not `TapirCirceJson`): @@ -185,7 +185,7 @@ Spray JSON requires a `JsonFormat` implicit value in scope for each type you wan To use [Tethys JSON](https://github.com/tethys-json/tethys) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-tethys" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-json-tethys" % "1.2.4" ``` Next, import the package (or extend the `TapirJsonTethys` trait, see [MyTapir](../mytapir.md) and add `TapirJsonTethys` not `TapirCirceJson`): @@ -201,7 +201,7 @@ Tethys JSON requires `JsonReader` and `JsonWriter` implicit values in scope for To use [Jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "1.2.4" ``` Next, import the package (or extend the `TapirJsonJsoniter` trait, see [MyTapir](../mytapir.md) and add `TapirJsonJsoniter` not `TapirCirceJson`): @@ -217,7 +217,7 @@ Jsoniter Scala requires `JsonValueCodec` implicit value in scope for each type y To use [json4s](https://github.com/json4s/json4s) add the following dependencies to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-json4s" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-json-json4s" % "1.2.4" ``` And one of the implementations: @@ -248,7 +248,7 @@ implicit val formats: Formats = org.json4s.jackson.Serialization.formats(NoTypeH To use [zio-json](https://github.com/zio/zio-json), add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "1.2.4" ``` Next, import the package (or extend the `TapirJsonZio` trait, see [MyTapir](../mytapir.md) and add `TapirJsonZio` instead of `TapirCirceJson`): @@ -258,6 +258,21 @@ import sttp.tapir.json.zio._ Zio JSON requires `JsonEncoder` and `JsonDecoder` implicit values in scope for each type you want to serialize. +## JSON query parameters + +You can specify query parameters in JSON format by using the `jsonQuery` method. For example, using Circe: + +```scala +import sttp.tapir._ +import sttp.tapir.json.circe._ +import sttp.tapir.generic.auto._ +import io.circe.generic.auto._ + +case class Book(author: String, title: String, year: Int) + +val bookQuery: EndpointInput.Query[Book] = jsonQuery[Book]("book") +``` + ## Other JSON libraries To add support for additional JSON libraries, see the @@ -271,8 +286,8 @@ of [schema derivation](schemas.md) will have to match the configuration of your have different defaults when it comes to a discrimination strategy, so in order to have the schemas (and hence the documentation) in sync with how the values are serialised, you will have to configure schema derivation as well. -Schemas are referenced at the point of `jsonBody` usage, so any configuration must be available in the implicit scope -when this method is called. +Schemas are referenced at the point of `jsonBody` and `jsonQuery` usage, so any configuration must be available in the implicit scope +when these methods are called. ## Next diff --git a/generated-doc/out/endpoint/security.md b/generated-doc/out/endpoint/security.md index 7ddb59cb37..caad4d5539 100644 --- a/generated-doc/out/endpoint/security.md +++ b/generated-doc/out/endpoint/security.md @@ -33,10 +33,7 @@ base64-encoded username/password combination, use: `basic[UsernamePassword]`. as a string, use: `bearer[String]`. * `auth.oauth2.authorizationCode(authorizationUrl, tokenUrl, scopes, refreshUrl): EndpointInput[String]`: creates an OAuth2 authorization using authorization code - sign in using an auth service (for documentation, requires defining also -the `oauth2-redirect.html`, see [Generating OpenAPI documentation](../openapi.md)) - -Optional and multiple authentication inputs have some additional rules as to how hey map to documentation, see the -[OpenAPI](../docs/openapi.md) page for details. +the `oauth2-redirect.html`, see [Generating OpenAPI documentation](..docs/openapi.md)) ## Authentication challenges @@ -47,6 +44,11 @@ status codes when authentication is missing, the decode failure handler can [be For example, if you define `endpoint.get.securityIn("path").securityIn(auth.basic[UsernamePassword]())` then the browser will show you a password prompt. +## Grouping authentication inputs in docs + +Optional and multiple authentication inputs have some additional rules as to how hey map to documentation, see the +["Authentication inputs and security requirements"](../docs/openapi.md) section in the OpenAPI docs for details. + ## Next Read on about [streaming support](streaming.md). diff --git a/generated-doc/out/generator/sbt-openapi-codegen.md b/generated-doc/out/generator/sbt-openapi-codegen.md index 80cadbcc15..c37aa54494 100644 --- a/generated-doc/out/generator/sbt-openapi-codegen.md +++ b/generated-doc/out/generator/sbt-openapi-codegen.md @@ -11,7 +11,7 @@ Add the sbt plugin to the `project/plugins.sbt`: ```scala -addSbtPlugin("com.softwaremill.sttp.tapir" % "sbt-openapi-codegen" % "1.2.3") +addSbtPlugin("com.softwaremill.sttp.tapir" % "sbt-openapi-codegen" % "1.2.4") ``` Enable the plugin for your project in the `build.sbt`: diff --git a/generated-doc/out/index.md b/generated-doc/out/index.md index 4f24382569..5e9d184f8f 100644 --- a/generated-doc/out/index.md +++ b/generated-doc/out/index.md @@ -11,7 +11,7 @@ input and output parameters. An endpoint specification can be interpreted as: Currently supported: * [Akka HTTP](server/akkahttp.md) `Route`s/`Directive`s * [Http4s](server/http4s.md) `HttpRoutes[F]` (using cats-effect or [ZIO](server/zio-http4s.md)) - * [Netty](server/netty.md) (using `Future`s or cats-effect) + * [Netty](server/netty.md) (using `Future`s, cats-effect or ZIO) * [Finatra](server/finatra.md) `http.Controller` * [Play](server/play.md) `Route` * [Vert.X](server/vertx.md) `Router => Route` (using `Future`s, cats-effect or ZIO) diff --git a/generated-doc/out/quickstart.md b/generated-doc/out/quickstart.md index 6e09752233..9585bd4695 100644 --- a/generated-doc/out/quickstart.md +++ b/generated-doc/out/quickstart.md @@ -3,7 +3,7 @@ To use tapir, add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.4" ``` This will import only the core classes needed to create endpoint descriptions. To generate a server or a client, you diff --git a/generated-doc/out/server/akkahttp.md b/generated-doc/out/server/akkahttp.md index 091d547d4a..4a965d133b 100644 --- a/generated-doc/out/server/akkahttp.md +++ b/generated-doc/out/server/akkahttp.md @@ -4,14 +4,14 @@ To expose an endpoint as an [akka-http](https://doc.akka.io/docs/akka-http/curre dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.4" ``` This will transitively pull some Akka modules in version 2.6. If you want to force your own Akka version (for example 2.5), use sbt exclusion. Mind the Scala version in artifact name: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.3" exclude("com.typesafe.akka", "akka-stream_2.12") +"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.4" exclude("com.typesafe.akka", "akka-stream_2.12") ``` Now import the object: diff --git a/generated-doc/out/server/armeria.md b/generated-doc/out/server/armeria.md index 60474b93d7..19eb092bf7 100644 --- a/generated-doc/out/server/armeria.md +++ b/generated-doc/out/server/armeria.md @@ -8,7 +8,7 @@ Armeria interpreter can be used with different effect systems (cats-effect, ZIO) Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server" % "1.2.4" ``` and import the object: @@ -75,7 +75,7 @@ Note that Armeria automatically injects an `ExecutionContext` on top of Armeria' Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-cats" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-cats" % "1.2.4" ``` to use this interpreter with Cats Effect typeclasses. @@ -155,9 +155,9 @@ Add the following dependency ```scala // for zio 2: -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio" % "1.2.4" // for zio 1: -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio1" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio1" % "1.2.4" ``` to use this interpreter with ZIO. diff --git a/generated-doc/out/server/aws.md b/generated-doc/out/server/aws.md index bdbd6af3a5..3882100054 100644 --- a/generated-doc/out/server/aws.md +++ b/generated-doc/out/server/aws.md @@ -13,7 +13,7 @@ To implement the Lambda function, a server interpreter is available, which takes Currently, only an interpreter integrating with cats-effect is available (`AwsCatsEffectServerInterpreter`). To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-aws-lambda" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-aws-lambda" % "1.2.4" ``` To configure API Gateway and the Lambda function, you can use: @@ -24,8 +24,8 @@ To configure API Gateway and the Lambda function, you can use: Add one of the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-aws-sam" % "1.2.3" -"com.softwaremill.sttp.tapir" %% "tapir-aws-terraform" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-aws-sam" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-aws-terraform" % "1.2.4" ``` ## Examples diff --git a/generated-doc/out/server/errors.md b/generated-doc/out/server/errors.md index 9bfa75891f..6a0aa220d0 100644 --- a/generated-doc/out/server/errors.md +++ b/generated-doc/out/server/errors.md @@ -112,9 +112,41 @@ an error or return a "no match", create error messages and create the response. swapped, e.g. to return responses in a different format (other than plain text), or customise the error messages. The default decode failure handler also has the option to return a `400 Bad Request`, instead of a no-match (ultimately -leading to a `404 Not Found`), when the "shape" of the path matches (that is, the number of segments in the request -and endpoint's paths are the same), but when decoding some part of the path ends in an error. See the -`badRequestOnPathErrorIfPathShapeMatches` in `ServerDefaults`. +leading to a `404 Not Found`), when the "shape" of the path matches (that is, the constant parts and number of segments +in the request and endpoint's paths are the same), but when decoding some part of the path ends in an error. See the +scaladoc for `DefaultDecodeFailureHandler.default` and parameters of `DefaultDecodeFailureHandler.response`. For example: + +```scala +import sttp.tapir._ +import sttp.tapir.server.akkahttp.AkkaHttpServerOptions +import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler +import scala.concurrent.ExecutionContext.Implicits.global + +val myDecodeFailureHandler = DefaultDecodeFailureHandler.default.copy( + respond = DefaultDecodeFailureHandler.respond( + _, + badRequestOnPathErrorIfPathShapeMatches = true, + badRequestOnPathInvalidIfPathShapeMatches = true + ) +) + +val myServerOptions: AkkaHttpServerOptions = AkkaHttpServerOptions + .customiseInterceptors + .decodeFailureHandler(myDecodeFailureHandler) + .options +``` + +Moreover, when using the `DefaultDecodeFailureHandler`, decode failure handling can be overriden on a per-input/output +basis, by setting an attribute. For example: + +```scala +import sttp.tapir._ +// bringing into scope the onDecodeFailureBadRequest extension method +import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler.OnDecodeFailure._ + +// by default, when the customer_id is not an int, the next endpoint would be tried; here, we always return a bad request +endpoint.in("customer" / path[Int]("customer_id").onDecodeFailureBadRequest) +``` ## Customising how error messages are rendered diff --git a/generated-doc/out/server/finatra.md b/generated-doc/out/server/finatra.md index 05f9896bdd..4a441eaddf 100644 --- a/generated-doc/out/server/finatra.md +++ b/generated-doc/out/server/finatra.md @@ -4,7 +4,7 @@ To expose an endpoint as an [finatra](https://twitter.github.io/finatra/) server dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-finatra-server" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-finatra-server" % "1.2.4" ``` and import the object: @@ -17,7 +17,7 @@ This interpreter supports the twitter `Future`. Or, if you would like to use cats-effect project, you can add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-finatra-server-cats" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-finatra-server-cats" % "1.2.4" ``` and import the object: diff --git a/generated-doc/out/server/http4s.md b/generated-doc/out/server/http4s.md index 22d491ff34..15a8a03e39 100644 --- a/generated-doc/out/server/http4s.md +++ b/generated-doc/out/server/http4s.md @@ -4,7 +4,7 @@ To expose an endpoint as an [http4s](https://http4s.org) server, first add the f dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % "1.2.4" ``` and import the object: diff --git a/generated-doc/out/server/netty.md b/generated-doc/out/server/netty.md index 72f256a441..a4f7144acb 100644 --- a/generated-doc/out/server/netty.md +++ b/generated-doc/out/server/netty.md @@ -4,16 +4,23 @@ To expose an endpoint using a [Netty](https://netty.io)-based server, first add ```scala // if you are using Future or just exploring -"com.softwaremill.sttp.tapir" %% "tapir-netty-server" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server" % "1.2.4" // if you are using cats-effect: -"com.softwaremill.sttp.tapir" %% "tapir-netty-server-cats" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-cats" % "1.2.4" + +// if you are using zio: +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio" % "1.2.4" + +// if you are using zio1: +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio1" % "1.2.4" ``` Then, use: * `NettyFutureServer().addEndpoints` to expose `Future`-based server endpoints. * `NettyCatsServer().addEndpoints` to expose `F`-based server endpoints, where `F` is any cats-effect supported effect. +* `NettyZioServer().addEndpoints` to expose `ZIO`-based server endpoints, where `R` represents ZIO requirements supported effect. These methods require a single, or a list of `ServerEndpoint`s, which can be created by adding [server logic](logic.md) to an endpoint. diff --git a/generated-doc/out/server/observability.md b/generated-doc/out/server/observability.md index 7ac782dbeb..cf441cc38f 100644 --- a/generated-doc/out/server/observability.md +++ b/generated-doc/out/server/observability.md @@ -49,7 +49,7 @@ val labels = MetricLabels( Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-prometheus-metrics" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-prometheus-metrics" % "1.2.4" ``` `PrometheusMetrics` encapsulates `CollectorReqistry` and `Metric` instances. It provides several ready to use metrics as @@ -130,7 +130,7 @@ val prometheusMetrics = PrometheusMetrics[Future]("tapir", CollectorRegistry.def Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-opentelemetry-metrics" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-opentelemetry-metrics" % "1.2.4" ``` OpenTelemetry metrics are vendor-agnostic and can be exported using one @@ -157,7 +157,7 @@ val metricsInterceptor = metrics.metricsInterceptor() // add to your server opti Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-datadog-metrics" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-datadog-metrics" % "1.2.4" ``` Datadog metrics are sent as Datadog custom metrics through @@ -224,7 +224,7 @@ val datadogMetrics = DatadogMetrics.default[Future](statsdClient) Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio-metrics" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-zio-metrics" % "1.2.4" ``` Metrics have been integrated into ZIO core in ZIO2. diff --git a/generated-doc/out/server/play.md b/generated-doc/out/server/play.md index 5efb224d7e..adf16f7af2 100644 --- a/generated-doc/out/server/play.md +++ b/generated-doc/out/server/play.md @@ -3,7 +3,7 @@ To expose endpoint as a [play-server](https://www.playframework.com/) first add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-play-server" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-play-server" % "1.2.4" ``` and (if you don't already depend on Play) diff --git a/generated-doc/out/server/vertx.md b/generated-doc/out/server/vertx.md index a5cbfe94d4..5c50e1727a 100644 --- a/generated-doc/out/server/vertx.md +++ b/generated-doc/out/server/vertx.md @@ -8,7 +8,7 @@ Vert.x interpreter can be used with different effect systems (cats-effect, ZIO) Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server" % "1.2.4" ``` to use this interpreter with `Future`. @@ -63,7 +63,7 @@ It's also possible to define an endpoint together with the server logic in a sin Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-cats" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-cats" % "1.2.4" ``` to use this interpreter with Cats Effect typeclasses. @@ -146,9 +146,9 @@ Add the following dependency ```scala // for zio2: -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio" % "1.2.4" // for zio1: -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio1" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio1" % "1.2.4" ``` to use this interpreter with ZIO. diff --git a/generated-doc/out/server/zio-http4s.md b/generated-doc/out/server/zio-http4s.md index f86dd4d81a..64ed6d6363 100644 --- a/generated-doc/out/server/zio-http4s.md +++ b/generated-doc/out/server/zio-http4s.md @@ -9,16 +9,16 @@ The `*-zio` modules depend on ZIO 2.x. For ZIO 1.x support, use modules with the You'll need the following dependency for the `ZServerEndpoint` type alias and helper classes: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.4" ``` or just add the zio-http4s integration which already depends on `tapir-zio`: ```scala // for zio 2: -"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio" % "1.2.4" // for zio 1: -"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio1" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio1" % "1.2.4" ``` Next, instead of the usual `import sttp.tapir._`, you should import (or extend the `ZTapir` trait, see [MyTapir](../mytapir.md)): diff --git a/generated-doc/out/server/ziohttp.md b/generated-doc/out/server/ziohttp.md index 7cf9dc945c..333002d7ca 100644 --- a/generated-doc/out/server/ziohttp.md +++ b/generated-doc/out/server/ziohttp.md @@ -9,13 +9,13 @@ The `*-zio` modules depend on ZIO 2.x. For ZIO 1.x support, use modules with the You'll need the following dependency for the `ZServerEndpoint` type alias and helper classes: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.4" ``` or just add the zio-http integration which already depends on `tapir-zio`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio-http-server" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-zio-http-server" % "1.2.4" ``` Next, instead of the usual `import sttp.tapir._`, you should import (or extend the `ZTapir` trait, see [MyTapir](../mytapir.md)): diff --git a/generated-doc/out/testing.md b/generated-doc/out/testing.md index 3214188ef8..2625081597 100644 --- a/generated-doc/out/testing.md +++ b/generated-doc/out/testing.md @@ -23,7 +23,7 @@ Tapir builds upon the `SttpBackendStub` to enable stubbing using `Endpoint`s or dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.4" ``` Let's assume you are using the [akka http](server/akkahttp.md) interpreter. Given the following server endpoint: @@ -140,7 +140,7 @@ requests matching an endpoint, you can use the tapir `SttpBackendStub` extension Similarly as when testing server interpreters, add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.4" ``` And the following imports: @@ -195,7 +195,7 @@ with [mock-server](https://www.mock-server.com/) Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-mock-server" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-mock-server" % "1.2.4" ``` Imports: @@ -266,7 +266,7 @@ result == out To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-testing" % "1.2.3" +"com.softwaremill.sttp.tapir" %% "tapir-testing" % "1.2.4" ``` ### Shadowed endpoints From eb27736573af5350fbb3fc26668d6a3af493759f Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sat, 17 Dec 2022 00:15:52 +0000 Subject: [PATCH 041/120] Update http4s-blaze-client, ... to 0.23.13 --- project/Versions.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Versions.scala b/project/Versions.scala index 76bbbfa5a6..f57277f0c0 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -1,7 +1,7 @@ object Versions { val http4s = "0.23.16" - val http4sBlazeServer = "0.23.12" - val http4sBlazeClient = "0.23.12" + val http4sBlazeServer = "0.23.13" + val http4sBlazeClient = "0.23.13" val catsEffect = "3.4.2" val circe = "0.14.3" val circeGenericExtras = "0.14.3" From cd60ac941d047b3207fe11c3491d9ca7cc80a910 Mon Sep 17 00:00:00 2001 From: adamw Date: Tue, 20 Dec 2022 20:51:41 +0100 Subject: [PATCH 042/120] Clarify logging configuration --- doc/server/debugging.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/doc/server/debugging.md b/doc/server/debugging.md index 41f878cd6d..39ae2764be 100644 --- a/doc/server/debugging.md +++ b/doc/server/debugging.md @@ -4,17 +4,22 @@ When dealing with multiple endpoints, how to find out which endpoint handled a r handle a request? For this purpose, tapir provides optional logging. The logging options (and messages) can be customised by providing -an instance of the `ServerLog` class, which is part of [server options](options.md). +an instance of the `ServerLog` trait, which is part of [server options](options.md). -The default implementation, `DefaultServerLog`, can be customised using the following flags: +An instance of the default implementation, `DefaultServerLog`, is available in the companion object for the +interpreter's options class, e.g. `Http4sServerOptions.defaultServerLog` or `NettyZioServerOptions.defaultServerLog`. -1. `logWhenHandled`: when a request is handled by an endpoint, or when the inputs can't be decoded, and the decode +This instance can be customised using the following flags: + +1. `logWhenReceived`: log when a request is first received (default: `false`, `DEBUG` log) +2. `logWhenHandled`: log when a request is handled by an endpoint, or when the inputs can't be decoded, and the decode failure maps to a response (default: `true`, `DEBUG` log) -2. `logAllDecodeFailures`: when the inputs can't be decoded, and the decode failure doesn't map to a response (the next - endpoint will be tried; default: `false`, `DEBUG` log) -3. `logExceptions`: when there's an exception during evaluation of the server logic (default: `true`, `ERROR` log) +3. `logAllDecodeFailures`: log each time when the inputs can't be decoded, and the decode failure doesn't map to a + response (the next endpoint will be tried; default: `false`, `DEBUG` log) +4. `logLogicExceptions`: log when there's an exception during evaluation of the server logic (default: `true`, + `ERROR` log) -Logging all decode failures (2) might be helpful when debugging, but can also produce a large amount of logs, hence +Logging all decode failures (3) might be helpful when debugging, but can also produce a large amount of logs, hence it's disabled by default. Even if logging for a particular category (as described above) is set to `true`, normal logger rules apply - if you From ee62d08e2387e41faf14cf3e8df8bcf3be5f17a5 Mon Sep 17 00:00:00 2001 From: Adam Warski Date: Wed, 21 Dec 2022 10:12:09 +0100 Subject: [PATCH 043/120] Fix scala-steward --- .github/workflows/scala-steward.yml | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/.github/workflows/scala-steward.yml b/.github/workflows/scala-steward.yml index 35229339ab..7b59b458ec 100644 --- a/.github/workflows/scala-steward.yml +++ b/.github/workflows/scala-steward.yml @@ -8,22 +8,16 @@ on: jobs: scala-steward: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: 'temurin' java-version: 11 - - name: Cache sbt - uses: actions/cache@v2 - with: - path: | - ~/.sbt - ~/.ivy2/cache - ~/.coursier - key: sbt-cache-${{ runner.os }}-JVM-${{ hashFiles('project/build.properties') }} + cache: 'sbt' - name: Launch Scala Steward uses: scala-steward-org/scala-steward-action@v2 with: @@ -32,10 +26,3 @@ jobs: github-token: ${{ secrets.REPO_GITHUB_TOKEN }} repo-config: .scala-steward.conf ignore-opts-files: false - - name: Cleanup - run: | - rm -rf "$HOME/.ivy2/local" || true - find $HOME/.ivy2/cache -name "ivydata-*.properties" -delete || true - find $HOME/.ivy2/cache -name "*-LM-SNAPSHOT*" -delete || true - find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete || true - find $HOME/.sbt -name "*.lock" -delete || true From 81c550c696822ce4891035f96640e755cbf7eb62 Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 22 Dec 2022 18:33:03 +0100 Subject: [PATCH 044/120] Fix implementations for Scala 2 & 3 --- .../EndpointInputAnnotationsMacro.scala | 19 +---------- .../tapir/internal/AnnotationsMacros.scala | 34 ++++++++++--------- .../annotations/DeriveEndpointIOTest.scala | 14 ++++---- 3 files changed, 27 insertions(+), 40 deletions(-) diff --git a/core/src/main/scala-2/sttp/tapir/internal/EndpointInputAnnotationsMacro.scala b/core/src/main/scala-2/sttp/tapir/internal/EndpointInputAnnotationsMacro.scala index eae672d09c..b635bafb64 100644 --- a/core/src/main/scala-2/sttp/tapir/internal/EndpointInputAnnotationsMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/internal/EndpointInputAnnotationsMacro.scala @@ -22,24 +22,7 @@ private[tapir] class EndpointInputAnnotationsMacro(override val c: blackbox.Cont val util = new CaseClassUtil[c.type, A](c, "request endpoint") validateCaseClass(util) - if (util.fields.isEmpty) { - forEmptyCaseClass(util) - } else { - forCaseClass(util) - } - } - - private def forEmptyCaseClass[A: c.WeakTypeTag](util: CaseClassUtil[c.type,A]) = { - val path = util.classSymbol.annotations - .map(_.tree) - .collectFirst { case Apply(Select(New(tree), _), List(arg)) if tree.tpe <:< endpointInput => arg } - - if (path.isEmpty) { - c.abort(c.enclosingPosition, s"case class is empty and not annotated with @endpointInput") - } else if (path.exists(tree => tree.toString().contains('{') || tree.toString().contains('}'))) { - c.abort(c.enclosingPosition, s"case class is empty but has path arguments in @endpointInput ${path.get}") - } - c.Expr[EndpointInput[A]](q"stringToPath(${path.get}).mapTo[${util.classSymbol}]") + forCaseClass(util) } private def forCaseClass[A: c.WeakTypeTag](util: CaseClassUtil[c.type,A]) = { diff --git a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala index 93e1ee36ab..bb56ae021f 100644 --- a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala +++ b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala @@ -17,21 +17,21 @@ private[tapir] class AnnotationsMacros[T <: Product: Type](using q: Quotes) { private val caseClass = new CaseClass[q.type, T](using summon[Type[T]], q) def deriveEndpointInputImpl: Expr[EndpointInput[T]] = { - if (caseClass.fields.isEmpty) { - val path = caseClass - .extractOptStringArgFromAnnotation(endpointInputAnnotationSymbol) - .flatten - - if (path.exists(path => path.contains('{') && path.contains('}'))) { - report.throwError(s"${caseClass.name} is empty but @endpointInput contains a path variable") - } else { - '{ - stringToPath(${ - Expr(path.headOption.getOrElse("")) - }).mapTo[T] - } - } - } +// if (caseClass.fields.isEmpty) { +// val path = caseClass +// .extractOptStringArgFromAnnotation(endpointInputAnnotationSymbol) +// .flatten +// +// if (path.exists(path => path.contains('{') && path.contains('}'))) { +// report.throwError(s"${caseClass.name} is empty but @endpointInput contains a path variable") +// } else { +// '{ +// stringToPath(${ +// Expr(path.headOption.getOrElse("")) +// }).mapTo[T] +// } +// } +// } // the path inputs must be defined in the order as they appear in the argument to @endpointInput val pathSegments = caseClass .extractOptStringArgFromAnnotation(endpointInputAnnotationSymbol) @@ -462,7 +462,9 @@ private[tapir] class AnnotationsMacros[T <: Product: Type](using q: Quotes) { Select.unique(tExpr.asTerm, field.name).asExprOf[Any] } - if (inputIdxToFieldIdx.size > 1) { + if (inputIdxToFieldIdx.size == 0) { + '{ (t: T) => ().asInstanceOf[A] } + } else if (inputIdxToFieldIdx.size > 1) { '{ (t: T) => ${ Expr.ofTupleFromSeq(tupleArgs('t)) }.asInstanceOf[A] } } else { '{ (t: T) => ${ tupleArgs('t).head }.asInstanceOf[A] } diff --git a/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala b/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala index 75eab62732..69000e14c3 100644 --- a/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala +++ b/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala @@ -131,9 +131,6 @@ final case class TapirRequestTest13(@fileBody file: TapirFile) final case class TapirRequestTest14(@multipartBody form: Form) -@endpointInput("some/path") -final case class TapirRequestTest15() - final case class TapirResponseTest1( @header header1: Int, @@ -185,6 +182,9 @@ object TapirRequestTest16 { val testAttributeKey: AttributeKey[String] = AttributeKey[String] } +@endpointInput("some/path") +final case class TapirRequestTest17() + class DeriveEndpointIOTest extends AnyFlatSpec with Matchers with TableDrivenPropertyChecks with Tapir { "@endpointInput" should "derive correct input for @query, @cookie, @header" in { @@ -321,9 +321,11 @@ class DeriveEndpointIOTest extends AnyFlatSpec with Matchers with TableDrivenPro } it should "accept empty case classes when annotated with @endpointInput" in { - val expectedInput = stringToPath("some/path").mapTo[TapirRequestTest15] - val derived = EndpointInput.derived[TapirRequestTest15].asInstanceOf[EndpointInput.FixedPath[TapirRequestTest15]] - compareTransputs(EndpointInput.derived[TapirRequestTest15], expectedInput) shouldBe true + val expectedInput = ("some" / "path").mapTo[TapirRequestTest17] + val derived = EndpointInput.derived[TapirRequestTest17] + println("XXX " + derived) + println("YYY " + expectedInput) + compareTransputs(EndpointInput.derived[TapirRequestTest17], expectedInput) shouldBe true } it should "not compile if there is field without annotation" in { From 98f7b8a0cdc8ee8006026b9a7ed9432dfaf8761c Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 22 Dec 2022 18:34:19 +0100 Subject: [PATCH 045/120] Simplify macro --- .../sttp/tapir/internal/EndpointInputAnnotationsMacro.scala | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/src/main/scala-2/sttp/tapir/internal/EndpointInputAnnotationsMacro.scala b/core/src/main/scala-2/sttp/tapir/internal/EndpointInputAnnotationsMacro.scala index b635bafb64..069a29a822 100644 --- a/core/src/main/scala-2/sttp/tapir/internal/EndpointInputAnnotationsMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/internal/EndpointInputAnnotationsMacro.scala @@ -22,10 +22,6 @@ private[tapir] class EndpointInputAnnotationsMacro(override val c: blackbox.Cont val util = new CaseClassUtil[c.type, A](c, "request endpoint") validateCaseClass(util) - forCaseClass(util) - } - - private def forCaseClass[A: c.WeakTypeTag](util: CaseClassUtil[c.type,A]) = { val segments = util.classSymbol.annotations .map(_.tree) .collectFirst { case Apply(Select(New(tree), _), List(arg)) if tree.tpe <:< endpointInput => arg } From ca667383e7c913b159aad69ae7b93840393cf992 Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 22 Dec 2022 18:34:27 +0100 Subject: [PATCH 046/120] Remove sod --- .../scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala b/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala index 69000e14c3..a34dd9277a 100644 --- a/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala +++ b/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala @@ -323,8 +323,6 @@ class DeriveEndpointIOTest extends AnyFlatSpec with Matchers with TableDrivenPro it should "accept empty case classes when annotated with @endpointInput" in { val expectedInput = ("some" / "path").mapTo[TapirRequestTest17] val derived = EndpointInput.derived[TapirRequestTest17] - println("XXX " + derived) - println("YYY " + expectedInput) compareTransputs(EndpointInput.derived[TapirRequestTest17], expectedInput) shouldBe true } From ec68529ef6792bb9fc508c99c15f16f2c4777f15 Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 22 Dec 2022 18:35:07 +0100 Subject: [PATCH 047/120] Remove commented code --- .../sttp/tapir/internal/AnnotationsMacros.scala | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala index bb56ae021f..a26b190fdf 100644 --- a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala +++ b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala @@ -17,21 +17,6 @@ private[tapir] class AnnotationsMacros[T <: Product: Type](using q: Quotes) { private val caseClass = new CaseClass[q.type, T](using summon[Type[T]], q) def deriveEndpointInputImpl: Expr[EndpointInput[T]] = { -// if (caseClass.fields.isEmpty) { -// val path = caseClass -// .extractOptStringArgFromAnnotation(endpointInputAnnotationSymbol) -// .flatten -// -// if (path.exists(path => path.contains('{') && path.contains('}'))) { -// report.throwError(s"${caseClass.name} is empty but @endpointInput contains a path variable") -// } else { -// '{ -// stringToPath(${ -// Expr(path.headOption.getOrElse("")) -// }).mapTo[T] -// } -// } -// } // the path inputs must be defined in the order as they appear in the argument to @endpointInput val pathSegments = caseClass .extractOptStringArgFromAnnotation(endpointInputAnnotationSymbol) From b0fe25d4d86be97ac14b846562e2b17b6ca88003 Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 22 Dec 2022 18:41:23 +0100 Subject: [PATCH 048/120] More exact field checking in scala 3 --- core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala b/core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala index f73bad431b..5ff2195059 100644 --- a/core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala +++ b/core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala @@ -25,10 +25,9 @@ private[tapir] object MappingMacros { inline def checkFields[A, B](using m: Mirror.ProductOf[A]): Unit = inline (erasedValue[m.MirroredElemTypes], erasedValue[B]) match { - case _: (EmptyTuple, B) => () + case _: (EmptyTuple, Unit) => () case _: (B *: EmptyTuple, B) => () case _: (B, B) => () - case _: EmptyTuple => () case e => ComplietimeErrors.reportIncorrectMapping[B, A] } } From 17e627a453616a11a6e1324c0891726dff9d0591 Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 22 Dec 2022 18:42:56 +0100 Subject: [PATCH 049/120] Simplify Scala 3 code --- core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala | 1 - .../test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala | 1 - 2 files changed, 2 deletions(-) diff --git a/core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala b/core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala index 5ff2195059..813da76e01 100644 --- a/core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala +++ b/core/src/main/scala-3/sttp/tapir/internal/MappingMacros.scala @@ -12,7 +12,6 @@ private[tapir] object MappingMacros { val to: In => Out = t => inline erasedValue[In] match { case _: Tuple => mc.fromProduct(t.asInstanceOf[Tuple]) - case EmptyTuple => mc.fromProduct(EmptyTuple) case _ => mc.fromProduct(Tuple1(t)) } def from(out: Out): In = Tuple.fromProduct(out) match { diff --git a/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala b/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala index a34dd9277a..72fff7c742 100644 --- a/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala +++ b/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala @@ -322,7 +322,6 @@ class DeriveEndpointIOTest extends AnyFlatSpec with Matchers with TableDrivenPro it should "accept empty case classes when annotated with @endpointInput" in { val expectedInput = ("some" / "path").mapTo[TapirRequestTest17] - val derived = EndpointInput.derived[TapirRequestTest17] compareTransputs(EndpointInput.derived[TapirRequestTest17], expectedInput) shouldBe true } From 2aa874d51ef272eab5c2c9827a9b080bb1e17c43 Mon Sep 17 00:00:00 2001 From: vladimirkl <72238+vladimirkl@users.noreply.github.com> Date: Fri, 23 Dec 2022 22:23:35 +0300 Subject: [PATCH 050/120] Fix synchronous effect execution for ZIO2 Vert.X server --- .../vertx/zio/VertxZioServerInterpreter.scala | 2 +- .../server/vertx/zio/ZioVertxServerTest.scala | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/server/vertx-server/zio/src/main/scala/sttp/tapir/server/vertx/zio/VertxZioServerInterpreter.scala b/server/vertx-server/zio/src/main/scala/sttp/tapir/server/vertx/zio/VertxZioServerInterpreter.scala index 6a43564b44..100192495c 100644 --- a/server/vertx-server/zio/src/main/scala/sttp/tapir/server/vertx/zio/VertxZioServerInterpreter.scala +++ b/server/vertx-server/zio/src/main/scala/sttp/tapir/server/vertx/zio/VertxZioServerInterpreter.scala @@ -88,7 +88,7 @@ trait VertxZioServerInterpreter[R] extends CommonServerInterpreter { val canceler: FiberId => Exit[Throwable, Any] = { Unsafe.unsafe(implicit u => { - val value = runtime.unsafe.fork(result) + val value = runtime.unsafe.fork(ZIO.yieldNow *> result) (fiberId: FiberId) => runtime.unsafe.run(value.interruptAs(fiberId)).flattenExit }) } diff --git a/server/vertx-server/zio/src/test/scala/sttp/tapir/server/vertx/zio/ZioVertxServerTest.scala b/server/vertx-server/zio/src/test/scala/sttp/tapir/server/vertx/zio/ZioVertxServerTest.scala index a6dddf8cfc..52fc221cd3 100644 --- a/server/vertx-server/zio/src/test/scala/sttp/tapir/server/vertx/zio/ZioVertxServerTest.scala +++ b/server/vertx-server/zio/src/test/scala/sttp/tapir/server/vertx/zio/ZioVertxServerTest.scala @@ -7,10 +7,14 @@ import sttp.capabilities.zio.ZioStreams import sttp.monad.MonadError import sttp.tapir.server.tests._ import sttp.tapir.tests.{Test, TestSuite} -import _root_.zio.Task +import sttp.tapir.ztapir._ +import _root_.zio.{Task, ZIO} +import org.scalatest.OptionValues +import sttp.client3.basicRequest import sttp.tapir.ztapir.RIOMonadError +import org.scalatest.matchers.should.Matchers._ -class ZioVertxServerTest extends TestSuite { +class ZioVertxServerTest extends TestSuite with OptionValues { def vertxResource: Resource[IO, Vertx] = Resource.make(IO.delay(Vertx.vertx()))(vertx => IO.delay(vertx.close()).void) @@ -22,12 +26,21 @@ class ZioVertxServerTest extends TestSuite { new DefaultCreateServerTest(backend, interpreter) .asInstanceOf[DefaultCreateServerTest[Task, ZioStreams, VertxZioServerOptions[Task], Router => Route]] + def additionalTests(): List[Test] = List( + createServerTest.testServer( + endpoint.out(plainBody[String]), + "Do not execute effects on vert.x thread" + )((_: Unit) => ZIO.attempt(Thread.currentThread().getName).asRight) { (backend, baseUri) => + basicRequest.get(baseUri).send(backend).map(_.body.toOption.value should not include "vert.x-eventloop-thread") + } + ) + new AllServerTests(createServerTest, interpreter, backend, multipart = false, reject = false, options = false).tests() ++ new ServerMultipartTests( createServerTest, partContentTypeHeaderSupport = false, // README: doesn't seem supported but I may be wrong partOtherHeaderSupport = false - ).tests() ++ + ).tests() ++ additionalTests() ++ new ServerStreamingTests(createServerTest, ZioStreams).tests() } } From 1309c4c5e6df42c5196ea6010c6567011df65cc0 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sat, 24 Dec 2022 00:05:19 +0000 Subject: [PATCH 051/120] Update jsoniter-scala-core, ... to 2.20.0 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 738aacd5f3..9c842c5f5c 100644 --- a/build.sbt +++ b/build.sbt @@ -782,8 +782,8 @@ lazy val jsoniterScala: ProjectMatrix = (projectMatrix in file("json/jsoniter")) .settings( name := "tapir-jsoniter-scala", libraryDependencies ++= Seq( - "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.19.1", - "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.19.1" % Test, + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.20.0", + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.20.0" % Test, scalaTest.value % Test ) ) From ece72067a4c0d64c9266d23f5ed677686efdc097 Mon Sep 17 00:00:00 2001 From: Florian Meriaux Date: Wed, 28 Dec 2022 09:57:25 +0100 Subject: [PATCH 052/120] add content range codec --- core/src/main/scala/sttp/tapir/Codec.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/scala/sttp/tapir/Codec.scala b/core/src/main/scala/sttp/tapir/Codec.scala index 7ae10ad6fc..d8c7ea52cc 100644 --- a/core/src/main/scala/sttp/tapir/Codec.scala +++ b/core/src/main/scala/sttp/tapir/Codec.scala @@ -569,6 +569,10 @@ object Codec extends CodecExtensions with CodecExtensions2 with FormCodecMacros } }(_.toString) + implicit val contentRange: Codec[String, ContentRange, CodecFormat.TextPlain] = Codec.string.mapDecode { v => + DecodeResult.fromEitherString(v, ContentRange.parse(v)) + }(_.toString) + implicit val cacheDirective: Codec[String, List[CacheDirective], CodecFormat.TextPlain] = Codec.string.mapDecode { v => @tailrec def toEitherOrList[T, U](l: List[Either[T, U]], acc: List[U]): Either[T, List[U]] = l match { From 0813340afeafd29a829a79c537ef32e79bf4bbe5 Mon Sep 17 00:00:00 2001 From: Florian Meriaux Date: Wed, 28 Dec 2022 10:03:09 +0100 Subject: [PATCH 053/120] missing import --- core/src/main/scala/sttp/tapir/Codec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/sttp/tapir/Codec.scala b/core/src/main/scala/sttp/tapir/Codec.scala index d8c7ea52cc..b3b6ac2f83 100644 --- a/core/src/main/scala/sttp/tapir/Codec.scala +++ b/core/src/main/scala/sttp/tapir/Codec.scala @@ -8,7 +8,7 @@ import java.time._ import java.time.format.{DateTimeFormatter, DateTimeParseException} import java.util.{Base64, Date, UUID} import sttp.model._ -import sttp.model.headers.{CacheDirective, Cookie, CookieWithMeta, ETag, Range} +import sttp.model.headers.{CacheDirective, Cookie, CookieWithMeta, ETag, Range, ContentRange} import sttp.tapir.CodecFormat.{MultipartFormData, OctetStream, TextPlain, XWwwFormUrlencoded} import sttp.tapir.DecodeResult.Error.MultipartDecodeException import sttp.tapir.DecodeResult._ From 9be3cf7862c4fd848e85d88a11b2b655f5dfe5d8 Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 2 Jan 2023 16:56:06 +0100 Subject: [PATCH 054/120] Status code example --- .../StatusCodeNettyFutureServer.scala | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 examples/src/main/scala/sttp/tapir/examples/status_code/StatusCodeNettyFutureServer.scala diff --git a/examples/src/main/scala/sttp/tapir/examples/status_code/StatusCodeNettyFutureServer.scala b/examples/src/main/scala/sttp/tapir/examples/status_code/StatusCodeNettyFutureServer.scala new file mode 100644 index 0000000000..538725340c --- /dev/null +++ b/examples/src/main/scala/sttp/tapir/examples/status_code/StatusCodeNettyFutureServer.scala @@ -0,0 +1,89 @@ +package sttp.tapir.examples.status_code + +import sttp.model.{HeaderNames, StatusCode} +import sttp.tapir._ +import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding} + +import java.net.InetSocketAddress +import scala.concurrent.{Await, Future} +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration.Duration + +/** Three examples of how to return custom status codes */ +object StatusCodeNettyFutureServer extends App { + + // An endpoint which always responds with status code 308 + val fixedStatusCodeEndpoint = endpoint.get + .in("fixed") + .out(statusCode(StatusCode.PermanentRedirect)) + .out(header(HeaderNames.Location, "https://adopt-tapir.softwaremill.com")) + .serverLogicPure[Future](_ => Right(())) + + // + + // An endpoint which computes the status code to return as part of its server logic + val dynamicStatusCodeEndpoint = endpoint.get + .in("dynamic" / path[Int]("status_code")) + .errorOut(stringBody) + .out(statusCode) + .out(stringBody) + .serverLogicPure[Future](code => + StatusCode.safeApply(code) match { + // by default, the status code for an error output is 400 + case Left(_) => Left(s"Unknown status code: $code") + // by default, the status code for a successful output is 200; here, we are overriding it using the statusCode output + case Right(parsedStatusCode) => Right((parsedStatusCode, s"Responding with status code: $code")) + } + ) + + // + + sealed trait ErrorInfo + case class NotFound(what: String) extends ErrorInfo + case class Unauthorized(realm: String) extends ErrorInfo + case object Unknown extends ErrorInfo + + // An endpoint which determines the status code basing on the type of the error output returned by the server logic (if any) + val oneOfStatusCodeEndpoint = endpoint + .in("oneof") + .in(query[Int]("kind")) + .errorOut( + oneOf[ErrorInfo]( + oneOfVariant(statusCode(StatusCode.NotFound).and(stringBody.mapTo[NotFound])), + oneOfVariant(statusCode(StatusCode.Unauthorized).and(stringBody.mapTo[Unauthorized])), + oneOfDefaultVariant(emptyOutputAs(Unknown)) // by default, the status code is 400 + ) + ) + .serverLogicPure[Future](kind => + kind match { + case 1 => Left(NotFound("not found")) // status code 404, as defined in oneOfVariant + case 2 => Left(Unauthorized("secret realm")) // status code 401, as defined in oneOfVariant + case 3 => Right(()) // status code 200 + case _ => Left(Unknown) // status code 400 + } + ) + + // + + // Starting netty server + val declaredPort = 8080 + val declaredHost = "localhost" + val serverBinding: NettyFutureServerBinding[InetSocketAddress] = + Await.result( + NettyFutureServer() + .port(declaredPort) + .host(declaredHost) + .addEndpoints(List(fixedStatusCodeEndpoint, dynamicStatusCodeEndpoint, oneOfStatusCodeEndpoint)) + .start(), + Duration.Inf + ) + + // Bind and start to accept incoming connections. + val port = serverBinding.port + val host = serverBinding.hostName + println(s"Server started at http://$host:$port") + println("Press any key to stop") + System.in.read() + + Await.result(serverBinding.stop(), Duration.Inf) +} From 4366eea2a95db0dca83624d6832366642f316d53 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Tue, 3 Jan 2023 00:18:39 +0000 Subject: [PATCH 055/120] Update model:core to 1.5.4 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 76bbbfa5a6..049b60eafb 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -7,7 +7,7 @@ object Versions { val circeGenericExtras = "0.14.3" val circeYaml = "0.14.2" val sttp = "3.8.5" - val sttpModel = "1.5.3" + val sttpModel = "1.5.4" val sttpShared = "1.3.12" val sttpApispec = "0.3.1" val akkaHttp = "10.2.10" From f4f08b10939337bfb88f7b7be7bb06d901afb588 Mon Sep 17 00:00:00 2001 From: Anders Dalvander Date: Wed, 4 Jan 2023 16:36:29 +0100 Subject: [PATCH 056/120] Change that ServerLog.requestReceived takes a token as the other callbacks --- .../server/interceptor/log/ServerLog.scala | 24 +++++++++---------- .../log/ServerLogInterceptor.scala | 5 ++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/server/core/src/main/scala/sttp/tapir/server/interceptor/log/ServerLog.scala b/server/core/src/main/scala/sttp/tapir/server/interceptor/log/ServerLog.scala index 7cb037533c..adafc5004d 100644 --- a/server/core/src/main/scala/sttp/tapir/server/interceptor/log/ServerLog.scala +++ b/server/core/src/main/scala/sttp/tapir/server/interceptor/log/ServerLog.scala @@ -18,12 +18,12 @@ trait ServerLog[F[_]] { */ type TOKEN - /** Invoked when the request has been received. */ - def requestReceived(request: ServerRequest): F[Unit] - /** Invoked when the request has been received to obtain the per-request token, such as the starting timestamp. */ def requestToken: TOKEN + /** Invoked when the request has been received. */ + def requestReceived(request: ServerRequest, token: TOKEN): F[Unit] + /** Invoked when there's a decode failure for an input of the endpoint and the interpreter, or other interceptors, haven't provided a * response. */ @@ -79,12 +79,12 @@ case class DefaultServerLog[F[_]]( override type TOKEN = Long - override def requestReceived(request: ServerRequest): F[Unit] = - if (logWhenReceived) doLogWhenReceived(s"Request received: ${showRequest(request)}") else noLog + override def requestToken: TOKEN = if (includeTiming) now() else 0 - override def requestToken: Long = if (includeTiming) now() else 0 + override def requestReceived(request: ServerRequest, token: TOKEN): F[Unit] = + if (logWhenReceived) doLogWhenReceived(s"Request received: ${showRequest(request)}") else noLog - override def decodeFailureNotHandled(ctx: DecodeFailureContext, token: Long): F[Unit] = + override def decodeFailureNotHandled(ctx: DecodeFailureContext, token: TOKEN): F[Unit] = if (logAllDecodeFailures) doLogAllDecodeFailures( s"Request: ${showRequest(ctx.request)}, not handled by: ${showEndpoint(ctx.endpoint)}${took(token)}; decode failure: ${ctx.failure}, on input: ${ctx.failingInput.show}", @@ -92,7 +92,7 @@ case class DefaultServerLog[F[_]]( ) else noLog - override def decodeFailureHandled(ctx: DecodeFailureContext, response: ServerResponse[_], token: Long): F[Unit] = + override def decodeFailureHandled(ctx: DecodeFailureContext, response: ServerResponse[_], token: TOKEN): F[Unit] = if (logWhenHandled) doLogWhenHandled( s"Request: ${showRequest(ctx.request)}, handled by: ${showEndpoint( @@ -102,7 +102,7 @@ case class DefaultServerLog[F[_]]( ) else noLog - override def securityFailureHandled(ctx: SecurityFailureContext[F, _], response: ServerResponse[_], token: Long): F[Unit] = + override def securityFailureHandled(ctx: SecurityFailureContext[F, _], response: ServerResponse[_], token: TOKEN): F[Unit] = if (logWhenHandled) doLogWhenHandled( s"Request: ${showRequest(ctx.request)}, handled by: ${showEndpoint(ctx.endpoint)}${took(token)}; security logic error response: ${showResponse(response)}", @@ -110,7 +110,7 @@ case class DefaultServerLog[F[_]]( ) else noLog - override def requestHandled(ctx: DecodeSuccessContext[F, _, _, _], response: ServerResponse[_], token: Long): F[Unit] = + override def requestHandled(ctx: DecodeSuccessContext[F, _, _, _], response: ServerResponse[_], token: TOKEN): F[Unit] = if (logWhenHandled) doLogWhenHandled( s"Request: ${showRequest(ctx.request)}, handled by: ${showEndpoint(ctx.endpoint)}${took(token)}; response: ${showResponse(response)}", @@ -118,14 +118,14 @@ case class DefaultServerLog[F[_]]( ) else noLog - override def exception(ctx: ExceptionContext[_, _], ex: Throwable, token: Long): F[Unit] = + override def exception(ctx: ExceptionContext[_, _], ex: Throwable, token: TOKEN): F[Unit] = if (logLogicExceptions) doLogExceptions(s"Exception when handling request: ${showRequest(ctx.request)}, by: ${showEndpoint(ctx.endpoint)}${took(token)}", ex) else noLog private def now() = clock.instant().toEpochMilli - private def took(token: Long): String = if (includeTiming) s", took: ${now() - token}ms" else "" + private def took(token: TOKEN): String = if (includeTiming) s", took: ${now() - token}ms" else "" private def exception(ctx: DecodeFailureContext): Option[Throwable] = ctx.failure match { diff --git a/server/core/src/main/scala/sttp/tapir/server/interceptor/log/ServerLogInterceptor.scala b/server/core/src/main/scala/sttp/tapir/server/interceptor/log/ServerLogInterceptor.scala index e987f5a80b..5926728a39 100644 --- a/server/core/src/main/scala/sttp/tapir/server/interceptor/log/ServerLogInterceptor.scala +++ b/server/core/src/main/scala/sttp/tapir/server/interceptor/log/ServerLogInterceptor.scala @@ -14,12 +14,13 @@ class ServerLogInterceptor[F[_]](serverLog: ServerLog[F]) extends RequestInterce responder: Responder[F, B], requestHandler: EndpointInterceptor[F] => RequestHandler[F, R, B] ): RequestHandler[F, R, B] = { - val delegate = requestHandler(new ServerLogEndpointInterceptor[F, serverLog.TOKEN](serverLog, serverLog.requestToken)) + val token = serverLog.requestToken + val delegate = requestHandler(new ServerLogEndpointInterceptor[F, serverLog.TOKEN](serverLog, token)) new RequestHandler[F, R, B] { override def apply(request: ServerRequest, endpoints: List[ServerEndpoint[R, F]])(implicit monad: MonadError[F] ): F[RequestResult[B]] = { - serverLog.requestReceived(request).flatMap(_ => delegate(request, endpoints)) + serverLog.requestReceived(request, token).flatMap(_ => delegate(request, endpoints)) } } } From 3bcbcb2c9f80069b80ff3c2171561da2352cade9 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Fri, 6 Jan 2023 00:06:06 +0000 Subject: [PATCH 057/120] Update client3:akka-http-backend, ... to 3.8.7 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 049b60eafb..7d5d0633da 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -6,7 +6,7 @@ object Versions { val circe = "0.14.3" val circeGenericExtras = "0.14.3" val circeYaml = "0.14.2" - val sttp = "3.8.5" + val sttp = "3.8.7" val sttpModel = "1.5.4" val sttpShared = "1.3.12" val sttpApispec = "0.3.1" From 3906c57cd0c32254946e0e6c3a3e3b3d75679748 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Fri, 6 Jan 2023 00:06:25 +0000 Subject: [PATCH 058/120] Update http4s-circe, http4s-core, ... to 0.23.17 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 049b60eafb..aaf2bcafc2 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -1,5 +1,5 @@ object Versions { - val http4s = "0.23.16" + val http4s = "0.23.17" val http4sBlazeServer = "0.23.12" val http4sBlazeClient = "0.23.12" val catsEffect = "3.4.2" From b6930848c2b7420428447fd17b35c270c0b65130 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sat, 7 Jan 2023 00:15:13 +0000 Subject: [PATCH 059/120] Update opentelemetry-api, ... to 1.22.0 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 0a399ad205..c84f4db9c4 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -50,7 +50,7 @@ object Versions { val fs2 = "3.4.0" val decline = "2.4.0" val quicklens = "1.9.0" - val openTelemetry = "1.20.1" + val openTelemetry = "1.22.0" val mockServer = "5.14.0" val dogstatsdClient = "4.1.0" val nettyAll = "4.1.86.Final" From ca1e331e5e5382d5efd5f3c954569eb862f54674 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sat, 7 Jan 2023 00:15:24 +0000 Subject: [PATCH 060/120] Revert commit(s) eb2773657 --- project/Versions.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Versions.scala b/project/Versions.scala index f57277f0c0..76bbbfa5a6 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -1,7 +1,7 @@ object Versions { val http4s = "0.23.16" - val http4sBlazeServer = "0.23.13" - val http4sBlazeClient = "0.23.13" + val http4sBlazeServer = "0.23.12" + val http4sBlazeClient = "0.23.12" val catsEffect = "3.4.2" val circe = "0.14.3" val circeGenericExtras = "0.14.3" From c5d7f9706bc86f5328a2f3f0d88b3a9d37fbbabe Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sat, 7 Jan 2023 00:15:27 +0000 Subject: [PATCH 061/120] Update http4s-blaze-client, ... to 0.23.13 --- project/Versions.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Versions.scala b/project/Versions.scala index 0a399ad205..a72a21b7dc 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -1,7 +1,7 @@ object Versions { val http4s = "0.23.17" - val http4sBlazeServer = "0.23.12" - val http4sBlazeClient = "0.23.12" + val http4sBlazeServer = "0.23.13" + val http4sBlazeClient = "0.23.13" val catsEffect = "3.4.2" val circe = "0.14.3" val circeGenericExtras = "0.14.3" From a56b5069987c2d5c1f52b11e836403045fecc175 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sat, 7 Jan 2023 00:15:32 +0000 Subject: [PATCH 062/120] Update scalatest to 3.2.15 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 0a399ad205..56f7a0bc60 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -20,7 +20,7 @@ object Versions { val json4s = "4.0.6" val sprayJson = "1.3.6" val scalaCheck = "1.17.0" - val scalaTest = "3.2.14" + val scalaTest = "3.2.15" val scalaTestPlusScalaCheck = "3.2.14.0" val refined = "0.10.1" val enumeratum = "1.7.2" From 64f696dbb94db5f95baf33429894ff4a493e27dd Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sat, 7 Jan 2023 00:15:43 +0000 Subject: [PATCH 063/120] Update scalacheck-1-17 to 3.2.15.0 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 0a399ad205..1d0f6c0f12 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -21,7 +21,7 @@ object Versions { val sprayJson = "1.3.6" val scalaCheck = "1.17.0" val scalaTest = "3.2.14" - val scalaTestPlusScalaCheck = "3.2.14.0" + val scalaTestPlusScalaCheck = "3.2.15.0" val refined = "0.10.1" val enumeratum = "1.7.2" val zio1 = "1.0.17" From 17672934612753a40c8bff48b0584a707734baad Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sun, 8 Jan 2023 00:16:27 +0000 Subject: [PATCH 064/120] Revert commit(s) 64f696dbb --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 1d0f6c0f12..0a399ad205 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -21,7 +21,7 @@ object Versions { val sprayJson = "1.3.6" val scalaCheck = "1.17.0" val scalaTest = "3.2.14" - val scalaTestPlusScalaCheck = "3.2.15.0" + val scalaTestPlusScalaCheck = "3.2.14.0" val refined = "0.10.1" val enumeratum = "1.7.2" val zio1 = "1.0.17" From 4227bf9cbbdc40459873592b6612b15672ba14be Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sun, 8 Jan 2023 00:16:33 +0000 Subject: [PATCH 065/120] Update scalacheck-1-17 to 3.2.15.0 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index deb50bba33..8634a3d8e3 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -21,7 +21,7 @@ object Versions { val sprayJson = "1.3.6" val scalaCheck = "1.17.0" val scalaTest = "3.2.15" - val scalaTestPlusScalaCheck = "3.2.14.0" + val scalaTestPlusScalaCheck = "3.2.15.0" val refined = "0.10.1" val enumeratum = "1.7.2" val zio1 = "1.0.17" From 0a0198a8a2317083653ca46705a3eca62bca829c Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 9 Jan 2023 15:00:26 +0100 Subject: [PATCH 066/120] Add fs2/zio dependencies to sttpclient+js --- build.sbt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 9c842c5f5c..a719df50dc 100644 --- a/build.sbt +++ b/build.sbt @@ -1559,7 +1559,11 @@ lazy val sttpClient: ProjectMatrix = (projectMatrix in file("client/sttp-client" scalaVersions = scala2And3Versions, settings = commonJsSettings ++ Seq( libraryDependencies ++= Seq( - "io.github.cquiroz" %%% "scala-java-time" % Versions.jsScalaJavaTime % Test + "io.github.cquiroz" %%% "scala-java-time" % Versions.jsScalaJavaTime % Test, + "com.softwaremill.sttp.client3" %%% "fs2" % Versions.sttp % Test, + "com.softwaremill.sttp.client3" %%% "zio" % Versions.sttp % Test, + "com.softwaremill.sttp.shared" %%% "fs2" % Versions.sttpShared % Optional, + "com.softwaremill.sttp.shared" %%% "zio" % Versions.sttpShared % Optional ) ) ) From 63e753b5623606b148f96840046d3d3772e20803 Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 9 Jan 2023 15:00:51 +0100 Subject: [PATCH 067/120] Add sttpclient interpreter ws integration for cats/zio + js --- .../ws/fs2/TapirSttpClientFs2WebSockets.scala | 11 +++ .../sttp/ws/fs2/WebSocketToFs2Pipe.scala | 97 +++++++++++++++++++ .../ws/zio/TapirSttpClientZioWebSockets.scala | 10 ++ .../sttp/ws/zio/WebSocketToZioPipe.scala | 93 ++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100644 client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/fs2/TapirSttpClientFs2WebSockets.scala create mode 100644 client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/fs2/WebSocketToFs2Pipe.scala create mode 100644 client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/zio/TapirSttpClientZioWebSockets.scala create mode 100644 client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/zio/WebSocketToZioPipe.scala diff --git a/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/fs2/TapirSttpClientFs2WebSockets.scala b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/fs2/TapirSttpClientFs2WebSockets.scala new file mode 100644 index 0000000000..4fdf5a659e --- /dev/null +++ b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/fs2/TapirSttpClientFs2WebSockets.scala @@ -0,0 +1,11 @@ +package sttp.tapir.client.sttp.ws.fs2 + +import cats.effect.Concurrent +import sttp.capabilities.WebSockets +import sttp.capabilities.fs2.Fs2Streams +import sttp.tapir.client.sttp.WebSocketToPipe + +trait TapirSttpClientFs2WebSockets { + implicit def webSocketsSupportedForFs2Streams[F[_]: Concurrent]: WebSocketToPipe[Fs2Streams[F] with WebSockets] = + new WebSocketToFs2Pipe[F, Fs2Streams[F] with WebSockets] +} diff --git a/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/fs2/WebSocketToFs2Pipe.scala b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/fs2/WebSocketToFs2Pipe.scala new file mode 100644 index 0000000000..320d218463 --- /dev/null +++ b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/fs2/WebSocketToFs2Pipe.scala @@ -0,0 +1,97 @@ +package sttp.tapir.client.sttp.ws.fs2 + +import _root_.fs2._ +import cats.MonadError +import cats.effect.Concurrent +import cats.syntax.all._ +import sttp.capabilities.WebSockets +import sttp.capabilities.fs2.Fs2Streams +import sttp.tapir.client.sttp.WebSocketToPipe +import sttp.tapir.model.WebSocketFrameDecodeFailure +import sttp.tapir.{DecodeResult, WebSocketBodyOutput} +import sttp.ws.{WebSocket, WebSocketFrame} + +import scala.reflect.ClassTag + +class WebSocketToFs2Pipe[_F[_]: Concurrent, R <: Fs2Streams[_F] with WebSockets] extends WebSocketToPipe[R] { + override type S = Fs2Streams[F] + override type F[X] = _F[X] + + override def apply[REQ, RESP](s: Any)(ws: WebSocket[F], o: WebSocketBodyOutput[Any, REQ, RESP, _, Fs2Streams[F]]): Any = { + (in: Stream[F, REQ]) => + val sends = in + .map(o.requests.encode) + .evalMap(ws.send(_, isContinuation = false)) // TODO support fragmented frames + + def decode(frame: WebSocketFrame): F[Either[Unit, Option[RESP]]] = + o.responses.decode(frame) match { + case failure: DecodeResult.Failure => + MonadError[F, Throwable].raiseError( + new WebSocketFrameDecodeFailure(frame, failure) + ) + case DecodeResult.Value(v) => (Right(Some(v)): Either[Unit, Option[RESP]]).pure[F] + } + + def raiseBadAccumulator[T](acc: WebSocketFrame, current: WebSocketFrame): F[T] = + MonadError[F, Throwable].raiseError( + new WebSocketFrameDecodeFailure( + current, + DecodeResult.Error( + "Bad frame sequence", + new Exception( + s"Invalid accumulator frame: $acc, it can't be concatenated with $current" + ) + ) + ) + ) + + def concatOrDecode[A <: WebSocketFrame: ClassTag]( + acc: Option[WebSocketFrame], + frame: A, + last: Boolean + )(f: (A, A) => A): F[(Option[WebSocketFrame], Either[Unit, Option[RESP]])] = + if (last) (acc match { + case None => decode(frame) + case Some(x: A) => decode(f(x, frame)) + case Some(bad) => raiseBadAccumulator(bad, frame) + }).map(none[WebSocketFrame] -> _) + else + (acc match { + case None => frame.some.pure[F] + case Some(x: A) => f(x, frame).some.pure[F] + case Some(bad) => raiseBadAccumulator(bad, frame) + }).map(acc => acc -> ().asLeft) + + val receives = Stream + .repeatEval(ws.receive()) + .evalMapAccumulate[F, Option[WebSocketFrame], Either[Unit, Option[RESP]]]( + none[WebSocketFrame] + ) { // left - ignore; right - close or response + case (acc, _: WebSocketFrame.Close) if !o.decodeCloseResponses => + (acc -> (Right(None): Either[Unit, Option[RESP]])).pure[F] + case (acc, _: WebSocketFrame.Pong) if o.ignorePong => + (acc -> (Left(()): Either[Unit, Option[RESP]])).pure[F] + case (acc, WebSocketFrame.Ping(p)) if o.autoPongOnPing => + ws.send(WebSocketFrame.Pong(p)) + .map(_ => acc -> (Left(()): Either[Unit, Option[RESP]])) + case (prev, frame @ WebSocketFrame.Text(_, last, _)) => + concatOrDecode(prev, frame, last)((l, r) => r.copy(payload = l.payload + r.payload)) + case (prev, frame @ WebSocketFrame.Binary(_, last, _)) => + concatOrDecode(prev, frame, last)((l, r) => r.copy(payload = l.payload ++ r.payload)) + case (_, frame) => + MonadError[F, Throwable].raiseError( + new WebSocketFrameDecodeFailure( + frame, + DecodeResult.Error( + "Unrecognised frame type", + new Exception(s"Unrecognised frame type: ${frame.getClass}") + ) + ) + ) + } + .collect { case (_, Right(d)) => d } + .unNoneTerminate + + sends.drain.merge(receives) + } +} diff --git a/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/zio/TapirSttpClientZioWebSockets.scala b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/zio/TapirSttpClientZioWebSockets.scala new file mode 100644 index 0000000000..83eff11e57 --- /dev/null +++ b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/zio/TapirSttpClientZioWebSockets.scala @@ -0,0 +1,10 @@ +package sttp.tapir.client.sttp.ws.zio + +import sttp.capabilities.WebSockets +import sttp.capabilities.zio.ZioStreams +import sttp.tapir.client.sttp.WebSocketToPipe + +trait TapirSttpClientZioWebSockets { + implicit val webSocketsSupportedForZioStreams: WebSocketToPipe[ZioStreams with WebSockets] = + new WebSocketToZioPipe[ZioStreams with WebSockets] +} diff --git a/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/zio/WebSocketToZioPipe.scala b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/zio/WebSocketToZioPipe.scala new file mode 100644 index 0000000000..4f289ee18b --- /dev/null +++ b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/zio/WebSocketToZioPipe.scala @@ -0,0 +1,93 @@ +package sttp.tapir.client.sttp.ws.zio + +import sttp.capabilities.WebSockets +import sttp.capabilities.zio.ZioStreams +import sttp.tapir.client.sttp.WebSocketToPipe +import sttp.tapir.model.WebSocketFrameDecodeFailure +import sttp.tapir.{DecodeResult, WebSocketBodyOutput} +import sttp.ws.{WebSocket, WebSocketFrame} +import zio.{Task, ZIO} +import zio.stream.{Stream, ZStream} + +import scala.reflect.ClassTag + +class WebSocketToZioPipe[R <: ZioStreams with WebSockets] extends WebSocketToPipe[R] { + override type S = ZioStreams + override type F[X] = Task[X] + + override def apply[REQ, RESP](s: Any)(ws: WebSocket[F], o: WebSocketBodyOutput[Any, REQ, RESP, _, ZioStreams]): Any = { + (in: Stream[Throwable, REQ]) => + val sends = in + .map(o.requests.encode) + .mapZIO(ws.send(_, isContinuation = false)) // TODO support fragmented frames + + def decode(frame: WebSocketFrame): F[Either[Unit, Option[RESP]]] = + o.responses.decode(frame) match { + case failure: DecodeResult.Failure => + ZIO.fail(new WebSocketFrameDecodeFailure(frame, failure)) + case DecodeResult.Value(v) => + ZIO.right[Option[RESP]](Some(v)) + } + + def raiseBadAccumulator[T](acc: WebSocketFrame, current: WebSocketFrame): F[T] = + ZIO.fail( + new WebSocketFrameDecodeFailure( + current, + DecodeResult.Error( + "Bad frame sequence", + new Exception( + s"Invalid accumulator frame: $acc, it can't be concatenated with $current" + ) + ) + ) + ) + + def concatOrDecode[A <: WebSocketFrame: ClassTag]( + acc: Option[WebSocketFrame], + frame: A, + last: Boolean + )(f: (A, A) => A): F[(Option[WebSocketFrame], Either[Unit, Option[RESP]])] = + if (last) (acc match { + case None => decode(frame) + case Some(x: A) => decode(f(x, frame)) + case Some(bad) => raiseBadAccumulator(bad, frame) + }).map(None -> _) + else + (acc match { + case None => ZIO.some(frame) + case Some(x: A) => ZIO.some(f(x, frame)) + case Some(bad) => raiseBadAccumulator(bad, frame) + }).map(acc => acc -> Left(())) + + val receives = ZStream + .repeatZIO(ws.receive()) + .mapAccumZIO[Any, Throwable, Option[WebSocketFrame], Either[Unit, Option[RESP]]]( + None + ) { // left - ignore; right - close or response + case (acc, _: WebSocketFrame.Close) if !o.decodeCloseResponses => + ZIO.succeed(acc -> Right(None)) + case (acc, _: WebSocketFrame.Pong) if o.ignorePong => + ZIO.succeed(acc -> Left(())) + case (acc, WebSocketFrame.Ping(p)) if o.autoPongOnPing => + ws.send(WebSocketFrame.Pong(p)).as(acc -> Left(())) + case (prev, frame @ WebSocketFrame.Text(_, last, _)) => + concatOrDecode(prev, frame, last)((l, r) => r.copy(payload = l.payload + r.payload)) + case (prev, frame @ WebSocketFrame.Binary(_, last, _)) => + concatOrDecode(prev, frame, last)((l, r) => r.copy(payload = l.payload ++ r.payload)) + case (_, frame) => + ZIO.fail( + new WebSocketFrameDecodeFailure( + frame, + DecodeResult.Error( + "Unrecognised frame type", + new Exception(s"Unrecognised frame type: ${frame.getClass}") + ) + ) + ) + } + .collectRight + .collectWhileSome + + sends.drain.merge(receives) + } +} From 4b84ede79b42d6f049f7e6204ad1f8bc5f91421f Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 9 Jan 2023 15:01:05 +0100 Subject: [PATCH 068/120] Add ws tests for zio + js --- .../sttp/SttpClientWebSocketZioTests.scala | 27 ++++++++ .../client/sttp/SttpClientZioTests.scala | 66 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 client/sttp-client/src/test/scalajs/sttp/tapir/client/sttp/SttpClientWebSocketZioTests.scala create mode 100644 client/sttp-client/src/test/scalajs/sttp/tapir/client/sttp/SttpClientZioTests.scala diff --git a/client/sttp-client/src/test/scalajs/sttp/tapir/client/sttp/SttpClientWebSocketZioTests.scala b/client/sttp-client/src/test/scalajs/sttp/tapir/client/sttp/SttpClientWebSocketZioTests.scala new file mode 100644 index 0000000000..8413afc72b --- /dev/null +++ b/client/sttp-client/src/test/scalajs/sttp/tapir/client/sttp/SttpClientWebSocketZioTests.scala @@ -0,0 +1,27 @@ +package sttp.tapir.client.sttp + +import cats.effect.IO +import sttp.capabilities.WebSockets +import sttp.capabilities.zio.ZioStreams +import sttp.tapir.client.sttp.ws.zio._ +import sttp.tapir.client.tests.ClientWebSocketTests +import zio.stream.{Stream, ZStream} + +class SttpClientWebSocketZioTests extends SttpClientZioTests[WebSockets with ZioStreams] with ClientWebSocketTests[ZioStreams] { + override val streams: ZioStreams = ZioStreams + override def wsToPipe: WebSocketToPipe[WebSockets with ZioStreams] = implicitly + + override def sendAndReceiveLimited[A, B]( + p: Stream[Throwable, A] => Stream[Throwable, B], + receiveCount: Port, + as: List[A] + ): IO[List[B]] = IO.fromFuture( + IO.delay { + unsafeToFuture( + ZStream(as: _*).viaFunction(p).take(receiveCount).runCollect.map(_.toList) + ).future + } + ) + + webSocketTests() +} diff --git a/client/sttp-client/src/test/scalajs/sttp/tapir/client/sttp/SttpClientZioTests.scala b/client/sttp-client/src/test/scalajs/sttp/tapir/client/sttp/SttpClientZioTests.scala new file mode 100644 index 0000000000..25263f0a34 --- /dev/null +++ b/client/sttp-client/src/test/scalajs/sttp/tapir/client/sttp/SttpClientZioTests.scala @@ -0,0 +1,66 @@ +package sttp.tapir.client.sttp + +import cats.effect.IO +import sttp.capabilities.WebSockets +import sttp.capabilities.zio.ZioStreams +import sttp.client3._ +import sttp.client3.impl.zio.FetchZioBackend +import sttp.tapir.client.tests.ClientTests +import sttp.tapir.{DecodeResult, Endpoint} +import zio.Runtime.default +import zio.{CancelableFuture, Task, Unsafe} + +abstract class SttpClientZioTests[R >: WebSockets with ZioStreams] extends ClientTests[R] { + private val runtime: default.UnsafeAPI = zio.Runtime.default.unsafe + val backend: SttpBackend[Task, R] = FetchZioBackend() + def wsToPipe: WebSocketToPipe[R] + + override def send[A, I, E, O]( + e: Endpoint[A, I, E, O, R], + port: Port, + securityArgs: A, + args: I, + scheme: String = "http" + ): IO[Either[E, O]] = + IO.fromFuture(IO.delay { + implicit val wst: WebSocketToPipe[R] = wsToPipe + val send = SttpClientInterpreter() + .toSecureRequestThrowDecodeFailures(e, Some(uri"$scheme://localhost:$port")) + .apply(securityArgs) + .apply(args) + .send(backend) + .map(_.body) + unsafeToFuture(send).future + }) + + override def safeSend[A, I, E, O]( + e: Endpoint[A, I, E, O, R], + port: Port, + securityArgs: A, + args: I + ): IO[DecodeResult[Either[E, O]]] = + IO.fromFuture(IO.delay { + implicit val wst: WebSocketToPipe[R] = wsToPipe + val send = SttpClientInterpreter() + .toSecureRequest(e, Some(uri"http://localhost:$port")) + .apply(securityArgs) + .apply(args) + .send(backend) + .map(_.body) + unsafeToFuture(send).future + }) + + override protected def afterAll(): Unit = { + unsafeRun(backend.close()) + super.afterAll() + } + + def unsafeRun[T](task: Task[T]): T = + Unsafe.unsafe { implicit u => + runtime.run(task).getOrThrowFiberFailure() + } + + def unsafeToFuture[T](task: Task[T]): CancelableFuture[T] = + Unsafe.unsafe(implicit u => runtime.runToFuture(task)) + +} From 1d1df73c63ab10f88dc7da2e18b7854f08828dad Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 9 Jan 2023 15:01:30 +0100 Subject: [PATCH 069/120] Determine if a ws request is successful differently on jvm / js --- .../sttp/tapir/client/sttp/EndpointToSttpClient.scala | 5 +++-- .../client/sttp/EndpointToSttpClientExtensions.scala | 9 +++++++++ .../scalajs/sttp/tapir/client/sttp/ws/fs2/package.scala | 3 +++ .../scalajs/sttp/tapir/client/sttp/ws/zio/package.scala | 3 +++ .../client/sttp/EndpointToSttpClientExtensions.scala | 9 +++++++++ .../client/sttp/EndpointToSttpClientExtensions.scala | 9 +++++++++ 6 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/EndpointToSttpClientExtensions.scala create mode 100644 client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/fs2/package.scala create mode 100644 client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/zio/package.scala create mode 100644 client/sttp-client/src/main/scalajvm/sttp/tapir/client/sttp/EndpointToSttpClientExtensions.scala create mode 100644 client/sttp-client/src/main/scalanative/sttp/tapir/client/sttp/EndpointToSttpClientExtensions.scala diff --git a/client/sttp-client/src/main/scala/sttp/tapir/client/sttp/EndpointToSttpClient.scala b/client/sttp-client/src/main/scala/sttp/tapir/client/sttp/EndpointToSttpClient.scala index daa656995b..779fb927dd 100644 --- a/client/sttp-client/src/main/scala/sttp/tapir/client/sttp/EndpointToSttpClient.scala +++ b/client/sttp-client/src/main/scala/sttp/tapir/client/sttp/EndpointToSttpClient.scala @@ -13,7 +13,8 @@ import java.io.ByteArrayInputStream import java.nio.ByteBuffer import scala.annotation.tailrec -private[sttp] class EndpointToSttpClient[R](clientOptions: SttpClientOptions, wsToPipe: WebSocketToPipe[R]) { +private[sttp] class EndpointToSttpClient[R](clientOptions: SttpClientOptions, wsToPipe: WebSocketToPipe[R]) + extends EndpointToSttpClientExtensions { def toSttpRequest[A, E, O, I](e: Endpoint[A, I, E, O, R], baseUri: Option[Uri]): A => I => Request[DecodeResult[Either[E, O]], R] = { aParams => iParams => val (uri1, req1) = @@ -40,7 +41,7 @@ private[sttp] class EndpointToSttpClient[R](clientOptions: SttpClientOptions, ws val isWebSocket = bodyIsWebSocket(e.output) - def isSuccess(meta: ResponseMetadata) = if (isWebSocket) meta.code == StatusCode.SwitchingProtocols else meta.isSuccess + def isSuccess(meta: ResponseMetadata) = if (isWebSocket) meta.code == webSocketSuccessStatusCode else meta.isSuccess val responseAs = fromMetadata( responseAsFromOutputs(e.errorOutput, isWebSocket = false), diff --git a/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/EndpointToSttpClientExtensions.scala b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/EndpointToSttpClientExtensions.scala new file mode 100644 index 0000000000..7515a57b63 --- /dev/null +++ b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/EndpointToSttpClientExtensions.scala @@ -0,0 +1,9 @@ +package sttp.tapir.client.sttp + +import sttp.model.StatusCode + +private[sttp] trait EndpointToSttpClientExtensions { this: EndpointToSttpClient[_] => + + /** This needs to be platform-specific due to #2663, as on JS we don't get access to the 101 status code. */ + val webSocketSuccessStatusCode: StatusCode = StatusCode.Ok +} diff --git a/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/fs2/package.scala b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/fs2/package.scala new file mode 100644 index 0000000000..02d90f4da7 --- /dev/null +++ b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/fs2/package.scala @@ -0,0 +1,3 @@ +package sttp.tapir.client.sttp.ws + +package object fs2 extends TapirSttpClientFs2WebSockets diff --git a/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/zio/package.scala b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/zio/package.scala new file mode 100644 index 0000000000..7faf24d84f --- /dev/null +++ b/client/sttp-client/src/main/scalajs/sttp/tapir/client/sttp/ws/zio/package.scala @@ -0,0 +1,3 @@ +package sttp.tapir.client.sttp.ws + +package object zio extends TapirSttpClientZioWebSockets diff --git a/client/sttp-client/src/main/scalajvm/sttp/tapir/client/sttp/EndpointToSttpClientExtensions.scala b/client/sttp-client/src/main/scalajvm/sttp/tapir/client/sttp/EndpointToSttpClientExtensions.scala new file mode 100644 index 0000000000..878080d282 --- /dev/null +++ b/client/sttp-client/src/main/scalajvm/sttp/tapir/client/sttp/EndpointToSttpClientExtensions.scala @@ -0,0 +1,9 @@ +package sttp.tapir.client.sttp + +import sttp.model.StatusCode + +private[sttp] trait EndpointToSttpClientExtensions { this: EndpointToSttpClient[_] => + + /** This needs to be platform-specific due to #2663, as on JS we don't get access to the 101 status code. */ + val webSocketSuccessStatusCode: StatusCode = StatusCode.SwitchingProtocols +} diff --git a/client/sttp-client/src/main/scalanative/sttp/tapir/client/sttp/EndpointToSttpClientExtensions.scala b/client/sttp-client/src/main/scalanative/sttp/tapir/client/sttp/EndpointToSttpClientExtensions.scala new file mode 100644 index 0000000000..878080d282 --- /dev/null +++ b/client/sttp-client/src/main/scalanative/sttp/tapir/client/sttp/EndpointToSttpClientExtensions.scala @@ -0,0 +1,9 @@ +package sttp.tapir.client.sttp + +import sttp.model.StatusCode + +private[sttp] trait EndpointToSttpClientExtensions { this: EndpointToSttpClient[_] => + + /** This needs to be platform-specific due to #2663, as on JS we don't get access to the 101 status code. */ + val webSocketSuccessStatusCode: StatusCode = StatusCode.SwitchingProtocols +} From ccc69f5d840eea7ccf80c476669817a554396ea8 Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 9 Jan 2023 15:01:41 +0100 Subject: [PATCH 070/120] Add note how to import only JS projects --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 38a775929e..0481847eb4 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,23 @@ You can test only server/client/doc/other projects using `testServers`, `testCli To verify that the code snippet in docs compile, run `compileDocumentation`. A full mdoc run is done during a release (when the documentation is generated). +### Importing into IntelliJ + +By default, when importing to IntelliJ, only the Scala 2.13/JVM subprojects will be imported. This is controlled by the `ideSkipProject` setting in `build.sbt` (inside `commonSettings`). + +If you'd like to work on a different platform or Scala version, simply change this setting temporarily so that the correct subprojects are imported. For example: + +``` +// import only Scala 2.13, JS projects +ideSkipProject := (scalaVersion.value != scala2_13) || !thisProjectRef.value.project.contains("JS") + +// import only Scala 3, JVM projects +ideSkipProject := (scalaVersion.value != scala3) || thisProjectRef.value.project.contains("JS") || thisProjectRef.value.project.contains("Native"), + +// import only Scala 2.13, Native projects +ideSkipProject := (scalaVersion.value != scala2_13) || !thisProjectRef.value.project.contains("Native") +``` + ## Commercial Support We offer commercial support for tapir and related technologies, as well as development services. [Contact us](https://softwaremill.com) to learn more about our offer! From 63bd649210793f1e1854d93e30495334e4273867 Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 9 Jan 2023 15:54:02 +0100 Subject: [PATCH 071/120] Attempt to make CI test less flaky by increasing the patience for eventually in ServerMetricsTest --- .../sttp/tapir/server/tests/ServerMetricsTest.scala | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerMetricsTest.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerMetricsTest.scala index 0a9ce1cba2..eef3338891 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerMetricsTest.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerMetricsTest.scala @@ -1,6 +1,7 @@ package sttp.tapir.server.tests import cats.implicits._ +import org.scalatest.concurrent.Eventually import org.scalatest.concurrent.Eventually.eventually import org.scalatest.matchers.should.Matchers._ import sttp.client3._ @@ -20,6 +21,12 @@ import java.util.concurrent.atomic.AtomicInteger class ServerMetricsTest[F[_], OPTIONS, ROUTE](createServerTest: CreateServerTest[F, Any, OPTIONS, ROUTE])(implicit m: MonadError[F]) { import createServerTest._ + // increase the patience for `eventually` for slow CI tests + implicit val patienceConfig: Eventually.PatienceConfig = Eventually.PatienceConfig( + timeout = org.scalatest.time.Span(15, org.scalatest.time.Seconds), + interval = org.scalatest.time.Span(150, org.scalatest.time.Millis) + ) + def tests(): List[Test] = List( { val reqCounter = newRequestCounter[F] @@ -36,8 +43,8 @@ class ServerMetricsTest[F[_], OPTIONS, ROUTE](createServerTest: CreateServerTest .send(backend) .map { r => r.body shouldBe Right("""{"fruit":"apple","amount":1}""") - eventually { - reqCounter.metric.value.get() shouldBe 1 + reqCounter.metric.value.get() shouldBe 1 + eventually { // the response metric is invoked *after* the body is sent, so we might have to wait resCounter.metric.value.get() shouldBe 1 } } >> basicRequest // onDecodeFailure path @@ -45,8 +52,8 @@ class ServerMetricsTest[F[_], OPTIONS, ROUTE](createServerTest: CreateServerTest .body("""{"invalid":"body",}""") .send(backend) .map { _ => + reqCounter.metric.value.get() shouldBe 2 eventually { - reqCounter.metric.value.get() shouldBe 2 resCounter.metric.value.get() shouldBe 2 } } From fbf8d001bbd1a5123f312af03cc8994a0786f0f8 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sat, 31 Dec 2022 00:05:27 +0000 Subject: [PATCH 072/120] Update cats-effect to 3.4.4 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index deb50bba33..638003e7bb 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -2,7 +2,7 @@ object Versions { val http4s = "0.23.17" val http4sBlazeServer = "0.23.12" val http4sBlazeClient = "0.23.12" - val catsEffect = "3.4.2" + val catsEffect = "3.4.4" val circe = "0.14.3" val circeGenericExtras = "0.14.3" val circeYaml = "0.14.2" From e19bf54ebfbec2ec7d1fd8b9d823e9a6cfe1ef04 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Fri, 30 Dec 2022 00:06:06 +0000 Subject: [PATCH 073/120] Update jwt-circe to 9.1.2 --- build.sbt | 2 +- project/Versions.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index a719df50dc..b5e6af1987 100644 --- a/build.sbt +++ b/build.sbt @@ -1683,7 +1683,7 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples")) "com.softwaremill.sttp.client3" %% "async-http-client-backend-zio" % Versions.sttp, "com.softwaremill.sttp.client3" %% "async-http-client-backend-cats" % Versions.sttp, "com.softwaremill.sttp.apispec" %% "asyncapi-circe-yaml" % Versions.sttpApispec, - "com.pauldijou" %% "jwt-circe" % Versions.jwtScala, + "com.github.jwt-scala" %% "jwt-circe" % Versions.jwtScala, "org.mock-server" % "mockserver-netty" % Versions.mockServer, "io.circe" %% "circe-generic-extras" % Versions.circeGenericExtras, scalaTest.value diff --git a/project/Versions.scala b/project/Versions.scala index deb50bba33..9b92a25281 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -38,7 +38,7 @@ object Versions { val vertx = "4.3.6" val jsScalaJavaTime = "2.5.0" val nativeScalaJavaTime = "2.4.0-M3" - val jwtScala = "5.0.0" + val jwtScala = "9.1.2" val derevo = "0.13.0" val newtype = "0.4.4" val monixNewtype = "0.2.3" From b1366ca07faa2dcd46ec26ac0a11debfb3e13d45 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Tue, 27 Dec 2022 00:16:08 +0000 Subject: [PATCH 074/120] Update finatra-http-server, inject-app, ... to 22.12.0 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index deb50bba33..1e5793ce34 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -15,7 +15,7 @@ object Versions { val swaggerUi = "4.15.5" val upickle = "2.0.0" val playJson = "2.9.3" - val finatra = "22.7.0" + val finatra = "22.12.0" val catbird = "21.12.0" val json4s = "4.0.6" val sprayJson = "1.3.6" From 0ae2f3626d3e7d16a1219edfc8564b0480ef0a25 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Fri, 23 Dec 2022 00:13:08 +0000 Subject: [PATCH 075/120] Update magnolia to 1.2.6 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index a719df50dc..0f5cfe9407 100644 --- a/build.sbt +++ b/build.sbt @@ -351,7 +351,7 @@ lazy val core: ProjectMatrix = (projectMatrix in file("core")) libraryDependencies ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((3, _)) => - Seq("com.softwaremill.magnolia1_3" %%% "magnolia" % "1.2.0") + Seq("com.softwaremill.magnolia1_3" %%% "magnolia" % "1.2.6") case _ => Seq( "com.softwaremill.magnolia1_2" %%% "magnolia" % "1.1.2", From e3a42327cf870073344b907894a1262a9f9df1b4 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Fri, 23 Dec 2022 00:12:56 +0000 Subject: [PATCH 076/120] Update armeria to 1.21.0 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index deb50bba33..e6bd25be62 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -44,7 +44,7 @@ object Versions { val monixNewtype = "0.2.3" val zioPrelude = "1.0.0-RC16" val awsLambdaInterface = "2.1.1" - val armeria = "1.20.3" + val armeria = "1.21.0" val scalaJava8Compat = "1.0.2" val scalaCollectionCompat = "2.9.0" val fs2 = "3.4.0" From 5262b80731df22f6398e48b208358ed8ae6a4159 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Wed, 21 Dec 2022 09:23:23 +0000 Subject: [PATCH 077/120] Update vertx-web to 4.3.7 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index deb50bba33..87801f555a 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -35,7 +35,7 @@ object Versions { val playClient = "2.1.10" val playServer = "2.8.18" val tethys = "0.26.0" - val vertx = "4.3.6" + val vertx = "4.3.7" val jsScalaJavaTime = "2.5.0" val nativeScalaJavaTime = "2.4.0-M3" val jwtScala = "5.0.0" From bc201c35b3fe6b3fb8505c3ea8c1c06afdc3d453 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sat, 17 Dec 2022 00:15:33 +0000 Subject: [PATCH 078/120] Update decline, decline-effect to 2.4.1 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index deb50bba33..5b1e15a5df 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -48,7 +48,7 @@ object Versions { val scalaJava8Compat = "1.0.2" val scalaCollectionCompat = "2.9.0" val fs2 = "3.4.0" - val decline = "2.4.0" + val decline = "2.4.1" val quicklens = "1.9.0" val openTelemetry = "1.22.0" val mockServer = "5.14.0" From 2e924e316c66a1895ceb1db24f6ab0dbd96c9ebd Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sat, 10 Dec 2022 00:14:03 +0000 Subject: [PATCH 079/120] Update sbt-assembly to 2.1.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index ce04cbd32a..41b8290a2e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -10,7 +10,7 @@ addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.9.0") addSbtPlugin("org.jetbrains.scala" % "sbt-ide-settings" % "1.1.1") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.12.0") addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") -addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.0.0") +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.0") addSbtPlugin("io.gatling" % "gatling-sbt" % "4.2.4") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.9") From 28cbdccb66cfacf5217f563f42836df034a1f5c1 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Thu, 15 Dec 2022 00:06:14 +0000 Subject: [PATCH 080/120] Update gatling-test-framework to 3.9.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index a719df50dc..3d54f44f0c 100644 --- a/build.sbt +++ b/build.sbt @@ -449,7 +449,7 @@ lazy val perfTests: ProjectMatrix = (projectMatrix in file("perf-tests")) name := "tapir-perf-tests", libraryDependencies ++= Seq( "io.gatling.highcharts" % "gatling-charts-highcharts" % "3.8.4" % "test", - "io.gatling" % "gatling-test-framework" % "3.8.4" % "test", + "io.gatling" % "gatling-test-framework" % "3.9.0" % "test", "com.typesafe.akka" %% "akka-http" % Versions.akkaHttp, "com.typesafe.akka" %% "akka-stream" % Versions.akkaStreams, "org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer, From 296d1a164ee6e49263488d640c4c13a6526ddf67 Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 9 Jan 2023 18:08:21 +0100 Subject: [PATCH 081/120] Increase build memory to 4G for native+scala3 --- .sbtopts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.sbtopts b/.sbtopts index f6f4bae375..76ec3a38df 100644 --- a/.sbtopts +++ b/.sbtopts @@ -1,3 +1,3 @@ --J-Xmx3500M +-J-Xmx4G -J-Xss2M -Dsbt.task.timings=false From 511d0249f0b3c2879b837c9907ab5a4ba0deae2b Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 9 Jan 2023 21:06:55 +0100 Subject: [PATCH 082/120] Release 1.2.5 --- README.md | 2 +- generated-doc/out/client/http4s.md | 2 +- generated-doc/out/client/play.md | 2 +- generated-doc/out/client/sttp.md | 4 ++-- generated-doc/out/docs/asyncapi.md | 2 +- generated-doc/out/docs/openapi.md | 12 ++++++------ generated-doc/out/endpoint/integrations.md | 14 +++++++------- generated-doc/out/endpoint/json.md | 16 ++++++++-------- .../out/generator/sbt-openapi-codegen.md | 2 +- generated-doc/out/quickstart.md | 2 +- generated-doc/out/server/akkahttp.md | 4 ++-- generated-doc/out/server/armeria.md | 8 ++++---- generated-doc/out/server/aws.md | 6 +++--- generated-doc/out/server/debugging.md | 19 ++++++++++++------- generated-doc/out/server/finatra.md | 4 ++-- generated-doc/out/server/http4s.md | 2 +- generated-doc/out/server/netty.md | 8 ++++---- generated-doc/out/server/observability.md | 8 ++++---- generated-doc/out/server/play.md | 2 +- generated-doc/out/server/vertx.md | 8 ++++---- generated-doc/out/server/zio-http4s.md | 6 +++--- generated-doc/out/server/ziohttp.md | 4 ++-- generated-doc/out/testing.md | 8 ++++---- 23 files changed, 75 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 0481847eb4..0fe666a37c 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ tapir documentation is available at [tapir.softwaremill.com](http://tapir.softwa Add the following dependency: ```sbt -"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.5" ``` Then, import: diff --git a/generated-doc/out/client/http4s.md b/generated-doc/out/client/http4s.md index ef213ac342..3b85c396db 100644 --- a/generated-doc/out/client/http4s.md +++ b/generated-doc/out/client/http4s.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-http4s-client" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-client" % "1.2.5" ``` To interpret an endpoint definition as an `org.http4s.Request[F]`, import: diff --git a/generated-doc/out/client/play.md b/generated-doc/out/client/play.md index 39e55314a2..fdafd852e2 100644 --- a/generated-doc/out/client/play.md +++ b/generated-doc/out/client/play.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-play-client" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-play-client" % "1.2.5" ``` To make requests using an endpoint definition using the [play client](https://github.com/playframework/play-ws), import: diff --git a/generated-doc/out/client/sttp.md b/generated-doc/out/client/sttp.md index bace01a4a9..904dc08c13 100644 --- a/generated-doc/out/client/sttp.md +++ b/generated-doc/out/client/sttp.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % "1.2.5" ``` To make requests using an endpoint definition using the [sttp client](https://github.com/softwaremill/sttp), import: @@ -102,7 +102,7 @@ In this case add the following dependencies (note the [`%%%`](https://www.scala- instead of the usual `%%`): ```scala -"com.softwaremill.sttp.tapir" %%% "tapir-sttp-client" % "1.2.4" +"com.softwaremill.sttp.tapir" %%% "tapir-sttp-client" % "1.2.5" "io.github.cquiroz" %%% "scala-java-time" % "2.2.0" // implementations of java.time classes for Scala.JS ``` diff --git a/generated-doc/out/docs/asyncapi.md b/generated-doc/out/docs/asyncapi.md index 4917beb966..6acb41f342 100644 --- a/generated-doc/out/docs/asyncapi.md +++ b/generated-doc/out/docs/asyncapi.md @@ -3,7 +3,7 @@ To use, add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-asyncapi-docs" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-asyncapi-docs" % "1.2.5" "com.softwaremill.sttp.apispec" %% "asyncapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` diff --git a/generated-doc/out/docs/openapi.md b/generated-doc/out/docs/openapi.md index 2c346f2a46..167f472564 100644 --- a/generated-doc/out/docs/openapi.md +++ b/generated-doc/out/docs/openapi.md @@ -13,7 +13,7 @@ these steps can be done separately, giving you complete control over the process To generate OpenAPI documentation and expose it using the Swagger UI in a single step, first add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % "1.2.5" ``` Then, you can interpret a list of endpoints using `SwaggerInterpreter`. The result will be a list of file-serving @@ -55,7 +55,7 @@ for details. Similarly as above, you'll need the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-redoc-bundle" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-redoc-bundle" % "1.2.5" ``` And the server endpoints can be generated using the `sttp.tapir.redoc.bundle.RedocInterpreter` class. @@ -65,7 +65,7 @@ And the server endpoints can be generated using the `sttp.tapir.redoc.bundle.Red To generate the docs in the OpenAPI yaml format, add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.5" "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` @@ -133,7 +133,7 @@ For example, generating the OpenAPI 3.1.0 YAML string can be achieved by perform Firstly add dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.5" "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` @@ -164,12 +164,12 @@ The modules `tapir-swagger-ui` and `tapir-redoc` contain server endpoint definit yaml format, will expose it using the given context path. To use, add as a dependency either `tapir-swagger-ui`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui" % "1.2.5" ``` or `tapir-redoc`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-redoc" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-redoc" % "1.2.5" ``` Then, you'll need to pass the server endpoints to your server interpreter. For example, using akka-http: diff --git a/generated-doc/out/endpoint/integrations.md b/generated-doc/out/endpoint/integrations.md index 4b035fcd82..e5169f85e6 100644 --- a/generated-doc/out/endpoint/integrations.md +++ b/generated-doc/out/endpoint/integrations.md @@ -14,7 +14,7 @@ The `tapir-cats` module contains additional instances for some [cats](https://ty datatypes as well as additional syntax: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-cats" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-cats" % "1.2.5" ``` - `import sttp.tapir.integ.cats.codec._` - brings schema, validator and codec instances @@ -26,7 +26,7 @@ If you use [refined](https://github.com/fthomas/refined), the `tapir-refined` mo validators for `T Refined P` as long as a codec for `T` already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-refined" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-refined" % "1.2.5" ``` You'll need to extend the `sttp.tapir.codec.refined.TapirCodecRefined` @@ -47,7 +47,7 @@ The `tapir-enumeratum` module provides schemas, validators and codecs for [Enume enumerations. To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % "1.2.5" ``` Then, `import sttp.tapir.codec.enumeratum._`, or extends the `sttp.tapir.codec.enumeratum.TapirCodecEnumeratum` trait. @@ -60,7 +60,7 @@ If you use [scala-newtype](https://github.com/estatico/scala-newtype), the `tapi schemas for types with a `@newtype` and `@newsubtype` annotations as long as a codec and schema for its underlying value already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-newtype" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-newtype" % "1.2.5" ``` Then, `import sttp.tapir.codec.newtype._`, or extend the `sttp.tapir.codec.newtype.TapirCodecNewType` trait to bring the implicit values into scope. @@ -71,7 +71,7 @@ If you use [monix newtypes](https://github.com/monix/newtypes), the `tapir-monix schemas for types which extend `NewtypeWrapped` and `NewsubtypeWrapped` annotations as long as a codec and schema for its underlying value already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-monix-newtype" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-monix-newtype" % "1.2.5" ``` Then, `import sttp.tapir.codec.monix.newtype._`, or extend the `sttp.tapir.codec.monix.newtype.TapirCodecMonixNewType` trait to bring the implicit values into scope. @@ -82,7 +82,7 @@ If you use [ZIO Prelude Newtypes](https://zio.github.io/zio-prelude/docs/newtype schemas for types defined using `Newtype` and `Subtype` as long as a codec and a schema for the underlying type already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio-prelude" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-zio-prelude" % "1.2.5" ``` Then, mix in `sttp.tapir.codec.zio.prelude.newtype.TapirNewtypeSupport` into your newtype to bring the implicit values into scope: @@ -121,7 +121,7 @@ For details refer to [derevo documentation](https://github.com/tofu-tf/derevo#in To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-derevo" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-derevo" % "1.2.5" ``` Then you can derive schema for your ADT along with other typeclasses besides ADT declaration itself: diff --git a/generated-doc/out/endpoint/json.md b/generated-doc/out/endpoint/json.md index 7820f44753..e4479efcba 100644 --- a/generated-doc/out/endpoint/json.md +++ b/generated-doc/out/endpoint/json.md @@ -45,7 +45,7 @@ stringJsonBody.schema(implicitly[Schema[MyBody]].as[String]) To use [Circe](https://github.com/circe/circe), add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.2.5" ``` Next, import the package (or extend the `TapirJsonCirce` trait, see [MyTapir](../mytapir.md)): @@ -118,7 +118,7 @@ Now the above JSON object will render as To use [µPickle](http://www.lihaoyi.com/upickle/) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-upickle" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-json-upickle" % "1.2.5" ``` Next, import the package (or extend the `TapirJsonuPickle` trait, see [MyTapir](../mytapir.md) and add `TapirJsonuPickle` not `TapirCirceJson`): @@ -153,7 +153,7 @@ For more examples, including making a custom encoder/decoder, see [TapirJsonuPic To use [Play JSON](https://github.com/playframework/play-json) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-play" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-json-play" % "1.2.5" ``` Next, import the package (or extend the `TapirJsonPlay` trait, see [MyTapir](../mytapir.md) and add `TapirJsonPlay` not `TapirCirceJson`): @@ -169,7 +169,7 @@ Play JSON requires `Reads` and `Writes` implicit values in scope for each type y To use [Spray JSON](https://github.com/spray/spray-json) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-spray" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-json-spray" % "1.2.5" ``` Next, import the package (or extend the `TapirJsonSpray` trait, see [MyTapir](../mytapir.md) and add `TapirJsonSpray` not `TapirCirceJson`): @@ -185,7 +185,7 @@ Spray JSON requires a `JsonFormat` implicit value in scope for each type you wan To use [Tethys JSON](https://github.com/tethys-json/tethys) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-tethys" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-json-tethys" % "1.2.5" ``` Next, import the package (or extend the `TapirJsonTethys` trait, see [MyTapir](../mytapir.md) and add `TapirJsonTethys` not `TapirCirceJson`): @@ -201,7 +201,7 @@ Tethys JSON requires `JsonReader` and `JsonWriter` implicit values in scope for To use [Jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "1.2.5" ``` Next, import the package (or extend the `TapirJsonJsoniter` trait, see [MyTapir](../mytapir.md) and add `TapirJsonJsoniter` not `TapirCirceJson`): @@ -217,7 +217,7 @@ Jsoniter Scala requires `JsonValueCodec` implicit value in scope for each type y To use [json4s](https://github.com/json4s/json4s) add the following dependencies to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-json4s" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-json-json4s" % "1.2.5" ``` And one of the implementations: @@ -248,7 +248,7 @@ implicit val formats: Formats = org.json4s.jackson.Serialization.formats(NoTypeH To use [zio-json](https://github.com/zio/zio-json), add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "1.2.5" ``` Next, import the package (or extend the `TapirJsonZio` trait, see [MyTapir](../mytapir.md) and add `TapirJsonZio` instead of `TapirCirceJson`): diff --git a/generated-doc/out/generator/sbt-openapi-codegen.md b/generated-doc/out/generator/sbt-openapi-codegen.md index c37aa54494..e62213a54a 100644 --- a/generated-doc/out/generator/sbt-openapi-codegen.md +++ b/generated-doc/out/generator/sbt-openapi-codegen.md @@ -11,7 +11,7 @@ Add the sbt plugin to the `project/plugins.sbt`: ```scala -addSbtPlugin("com.softwaremill.sttp.tapir" % "sbt-openapi-codegen" % "1.2.4") +addSbtPlugin("com.softwaremill.sttp.tapir" % "sbt-openapi-codegen" % "1.2.5") ``` Enable the plugin for your project in the `build.sbt`: diff --git a/generated-doc/out/quickstart.md b/generated-doc/out/quickstart.md index 9585bd4695..12df2a8872 100644 --- a/generated-doc/out/quickstart.md +++ b/generated-doc/out/quickstart.md @@ -3,7 +3,7 @@ To use tapir, add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.5" ``` This will import only the core classes needed to create endpoint descriptions. To generate a server or a client, you diff --git a/generated-doc/out/server/akkahttp.md b/generated-doc/out/server/akkahttp.md index 4a965d133b..e94cdefc09 100644 --- a/generated-doc/out/server/akkahttp.md +++ b/generated-doc/out/server/akkahttp.md @@ -4,14 +4,14 @@ To expose an endpoint as an [akka-http](https://doc.akka.io/docs/akka-http/curre dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.5" ``` This will transitively pull some Akka modules in version 2.6. If you want to force your own Akka version (for example 2.5), use sbt exclusion. Mind the Scala version in artifact name: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.4" exclude("com.typesafe.akka", "akka-stream_2.12") +"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.5" exclude("com.typesafe.akka", "akka-stream_2.12") ``` Now import the object: diff --git a/generated-doc/out/server/armeria.md b/generated-doc/out/server/armeria.md index 19eb092bf7..2b9dfd60f6 100644 --- a/generated-doc/out/server/armeria.md +++ b/generated-doc/out/server/armeria.md @@ -8,7 +8,7 @@ Armeria interpreter can be used with different effect systems (cats-effect, ZIO) Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server" % "1.2.5" ``` and import the object: @@ -75,7 +75,7 @@ Note that Armeria automatically injects an `ExecutionContext` on top of Armeria' Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-cats" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-cats" % "1.2.5" ``` to use this interpreter with Cats Effect typeclasses. @@ -155,9 +155,9 @@ Add the following dependency ```scala // for zio 2: -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio" % "1.2.5" // for zio 1: -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio1" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio1" % "1.2.5" ``` to use this interpreter with ZIO. diff --git a/generated-doc/out/server/aws.md b/generated-doc/out/server/aws.md index 3882100054..1a2a4b7a64 100644 --- a/generated-doc/out/server/aws.md +++ b/generated-doc/out/server/aws.md @@ -13,7 +13,7 @@ To implement the Lambda function, a server interpreter is available, which takes Currently, only an interpreter integrating with cats-effect is available (`AwsCatsEffectServerInterpreter`). To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-aws-lambda" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-aws-lambda" % "1.2.5" ``` To configure API Gateway and the Lambda function, you can use: @@ -24,8 +24,8 @@ To configure API Gateway and the Lambda function, you can use: Add one of the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-aws-sam" % "1.2.4" -"com.softwaremill.sttp.tapir" %% "tapir-aws-terraform" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-aws-sam" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-aws-terraform" % "1.2.5" ``` ## Examples diff --git a/generated-doc/out/server/debugging.md b/generated-doc/out/server/debugging.md index 41f878cd6d..39ae2764be 100644 --- a/generated-doc/out/server/debugging.md +++ b/generated-doc/out/server/debugging.md @@ -4,17 +4,22 @@ When dealing with multiple endpoints, how to find out which endpoint handled a r handle a request? For this purpose, tapir provides optional logging. The logging options (and messages) can be customised by providing -an instance of the `ServerLog` class, which is part of [server options](options.md). +an instance of the `ServerLog` trait, which is part of [server options](options.md). -The default implementation, `DefaultServerLog`, can be customised using the following flags: +An instance of the default implementation, `DefaultServerLog`, is available in the companion object for the +interpreter's options class, e.g. `Http4sServerOptions.defaultServerLog` or `NettyZioServerOptions.defaultServerLog`. -1. `logWhenHandled`: when a request is handled by an endpoint, or when the inputs can't be decoded, and the decode +This instance can be customised using the following flags: + +1. `logWhenReceived`: log when a request is first received (default: `false`, `DEBUG` log) +2. `logWhenHandled`: log when a request is handled by an endpoint, or when the inputs can't be decoded, and the decode failure maps to a response (default: `true`, `DEBUG` log) -2. `logAllDecodeFailures`: when the inputs can't be decoded, and the decode failure doesn't map to a response (the next - endpoint will be tried; default: `false`, `DEBUG` log) -3. `logExceptions`: when there's an exception during evaluation of the server logic (default: `true`, `ERROR` log) +3. `logAllDecodeFailures`: log each time when the inputs can't be decoded, and the decode failure doesn't map to a + response (the next endpoint will be tried; default: `false`, `DEBUG` log) +4. `logLogicExceptions`: log when there's an exception during evaluation of the server logic (default: `true`, + `ERROR` log) -Logging all decode failures (2) might be helpful when debugging, but can also produce a large amount of logs, hence +Logging all decode failures (3) might be helpful when debugging, but can also produce a large amount of logs, hence it's disabled by default. Even if logging for a particular category (as described above) is set to `true`, normal logger rules apply - if you diff --git a/generated-doc/out/server/finatra.md b/generated-doc/out/server/finatra.md index 4a441eaddf..bc2c8c3879 100644 --- a/generated-doc/out/server/finatra.md +++ b/generated-doc/out/server/finatra.md @@ -4,7 +4,7 @@ To expose an endpoint as an [finatra](https://twitter.github.io/finatra/) server dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-finatra-server" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-finatra-server" % "1.2.5" ``` and import the object: @@ -17,7 +17,7 @@ This interpreter supports the twitter `Future`. Or, if you would like to use cats-effect project, you can add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-finatra-server-cats" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-finatra-server-cats" % "1.2.5" ``` and import the object: diff --git a/generated-doc/out/server/http4s.md b/generated-doc/out/server/http4s.md index 15a8a03e39..a2e19689a5 100644 --- a/generated-doc/out/server/http4s.md +++ b/generated-doc/out/server/http4s.md @@ -4,7 +4,7 @@ To expose an endpoint as an [http4s](https://http4s.org) server, first add the f dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % "1.2.5" ``` and import the object: diff --git a/generated-doc/out/server/netty.md b/generated-doc/out/server/netty.md index a4f7144acb..923dca5511 100644 --- a/generated-doc/out/server/netty.md +++ b/generated-doc/out/server/netty.md @@ -4,16 +4,16 @@ To expose an endpoint using a [Netty](https://netty.io)-based server, first add ```scala // if you are using Future or just exploring -"com.softwaremill.sttp.tapir" %% "tapir-netty-server" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server" % "1.2.5" // if you are using cats-effect: -"com.softwaremill.sttp.tapir" %% "tapir-netty-server-cats" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-cats" % "1.2.5" // if you are using zio: -"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio" % "1.2.5" // if you are using zio1: -"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio1" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio1" % "1.2.5" ``` Then, use: diff --git a/generated-doc/out/server/observability.md b/generated-doc/out/server/observability.md index cf441cc38f..cf9ea58e35 100644 --- a/generated-doc/out/server/observability.md +++ b/generated-doc/out/server/observability.md @@ -49,7 +49,7 @@ val labels = MetricLabels( Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-prometheus-metrics" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-prometheus-metrics" % "1.2.5" ``` `PrometheusMetrics` encapsulates `CollectorReqistry` and `Metric` instances. It provides several ready to use metrics as @@ -130,7 +130,7 @@ val prometheusMetrics = PrometheusMetrics[Future]("tapir", CollectorRegistry.def Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-opentelemetry-metrics" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-opentelemetry-metrics" % "1.2.5" ``` OpenTelemetry metrics are vendor-agnostic and can be exported using one @@ -157,7 +157,7 @@ val metricsInterceptor = metrics.metricsInterceptor() // add to your server opti Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-datadog-metrics" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-datadog-metrics" % "1.2.5" ``` Datadog metrics are sent as Datadog custom metrics through @@ -224,7 +224,7 @@ val datadogMetrics = DatadogMetrics.default[Future](statsdClient) Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio-metrics" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-zio-metrics" % "1.2.5" ``` Metrics have been integrated into ZIO core in ZIO2. diff --git a/generated-doc/out/server/play.md b/generated-doc/out/server/play.md index adf16f7af2..4537183376 100644 --- a/generated-doc/out/server/play.md +++ b/generated-doc/out/server/play.md @@ -3,7 +3,7 @@ To expose endpoint as a [play-server](https://www.playframework.com/) first add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-play-server" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-play-server" % "1.2.5" ``` and (if you don't already depend on Play) diff --git a/generated-doc/out/server/vertx.md b/generated-doc/out/server/vertx.md index 5c50e1727a..f362701548 100644 --- a/generated-doc/out/server/vertx.md +++ b/generated-doc/out/server/vertx.md @@ -8,7 +8,7 @@ Vert.x interpreter can be used with different effect systems (cats-effect, ZIO) Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server" % "1.2.5" ``` to use this interpreter with `Future`. @@ -63,7 +63,7 @@ It's also possible to define an endpoint together with the server logic in a sin Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-cats" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-cats" % "1.2.5" ``` to use this interpreter with Cats Effect typeclasses. @@ -146,9 +146,9 @@ Add the following dependency ```scala // for zio2: -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio" % "1.2.5" // for zio1: -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio1" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio1" % "1.2.5" ``` to use this interpreter with ZIO. diff --git a/generated-doc/out/server/zio-http4s.md b/generated-doc/out/server/zio-http4s.md index 64ed6d6363..0a86be2011 100644 --- a/generated-doc/out/server/zio-http4s.md +++ b/generated-doc/out/server/zio-http4s.md @@ -9,16 +9,16 @@ The `*-zio` modules depend on ZIO 2.x. For ZIO 1.x support, use modules with the You'll need the following dependency for the `ZServerEndpoint` type alias and helper classes: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.5" ``` or just add the zio-http4s integration which already depends on `tapir-zio`: ```scala // for zio 2: -"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio" % "1.2.5" // for zio 1: -"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio1" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio1" % "1.2.5" ``` Next, instead of the usual `import sttp.tapir._`, you should import (or extend the `ZTapir` trait, see [MyTapir](../mytapir.md)): diff --git a/generated-doc/out/server/ziohttp.md b/generated-doc/out/server/ziohttp.md index 333002d7ca..3ceb92c6c1 100644 --- a/generated-doc/out/server/ziohttp.md +++ b/generated-doc/out/server/ziohttp.md @@ -9,13 +9,13 @@ The `*-zio` modules depend on ZIO 2.x. For ZIO 1.x support, use modules with the You'll need the following dependency for the `ZServerEndpoint` type alias and helper classes: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.5" ``` or just add the zio-http integration which already depends on `tapir-zio`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio-http-server" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-zio-http-server" % "1.2.5" ``` Next, instead of the usual `import sttp.tapir._`, you should import (or extend the `ZTapir` trait, see [MyTapir](../mytapir.md)): diff --git a/generated-doc/out/testing.md b/generated-doc/out/testing.md index 2625081597..484825c8c9 100644 --- a/generated-doc/out/testing.md +++ b/generated-doc/out/testing.md @@ -23,7 +23,7 @@ Tapir builds upon the `SttpBackendStub` to enable stubbing using `Endpoint`s or dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.5" ``` Let's assume you are using the [akka http](server/akkahttp.md) interpreter. Given the following server endpoint: @@ -140,7 +140,7 @@ requests matching an endpoint, you can use the tapir `SttpBackendStub` extension Similarly as when testing server interpreters, add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.5" ``` And the following imports: @@ -195,7 +195,7 @@ with [mock-server](https://www.mock-server.com/) Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-mock-server" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-mock-server" % "1.2.5" ``` Imports: @@ -266,7 +266,7 @@ result == out To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-testing" % "1.2.4" +"com.softwaremill.sttp.tapir" %% "tapir-testing" % "1.2.5" ``` ### Shadowed endpoints From 2f746ee925761481376ab1f3586d8b513a1ae483 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Tue, 10 Jan 2023 00:12:40 +0000 Subject: [PATCH 083/120] Update jsoniter-scala-core, ... to 2.20.2 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 973a734b09..e28d95604b 100644 --- a/build.sbt +++ b/build.sbt @@ -782,8 +782,8 @@ lazy val jsoniterScala: ProjectMatrix = (projectMatrix in file("json/jsoniter")) .settings( name := "tapir-jsoniter-scala", libraryDependencies ++= Seq( - "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.20.0", - "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.20.0" % Test, + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.20.2", + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.20.2" % Test, scalaTest.value % Test ) ) From 8a3ec5aa186694187de28533d8db7e72ced1faf8 Mon Sep 17 00:00:00 2001 From: Maciej Buszka Date: Tue, 10 Jan 2023 10:59:20 +0100 Subject: [PATCH 084/120] Cross compile tapir-enumeratum with Scala 3 --- build.sbt | 12 +++-- .../enumeratum/TapirCodecEnumeratum.scala | 48 +++++++++---------- .../enumeratum/TapirCodecEnumeratumTest.scala | 20 ++++---- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/build.sbt b/build.sbt index 973a734b09..ccde8f1d92 100644 --- a/build.sbt +++ b/build.sbt @@ -527,11 +527,17 @@ lazy val enumeratum: ProjectMatrix = (projectMatrix in file("integrations/enumer libraryDependencies ++= Seq( "com.beachape" %%% "enumeratum" % Versions.enumeratum, scalaTest.value % Test - ) + ), + scalacOptions ++= { + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((3, _)) => Seq("-Yretain-trees") + case _ => Seq() + } + } ) - .jvmPlatform(scalaVersions = scala2Versions) + .jvmPlatform(scalaVersions = scala2And3Versions) .jsPlatform( - scalaVersions = scala2Versions, + scalaVersions = scala2And3Versions, settings = commonJsSettings ++ Seq( libraryDependencies ++= Seq( "io.github.cquiroz" %%% "scala-java-time" % Versions.jsScalaJavaTime % Test diff --git a/integrations/enumeratum/src/main/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratum.scala b/integrations/enumeratum/src/main/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratum.scala index 9e44aed05e..4fdfd5ad50 100644 --- a/integrations/enumeratum/src/main/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratum.scala +++ b/integrations/enumeratum/src/main/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratum.scala @@ -8,13 +8,13 @@ import sttp.tapir._ trait TapirCodecEnumeratum { // Regular enums - def validatorEnumEntry[E <: EnumEntry](implicit enum: Enum[E]): Validator.Enumeration[E] = - Validator.enumeration(enum.values.toList, v => Some(v.entryName), Some(SName(fullName(`enum`)))) + def validatorEnumEntry[E <: EnumEntry](implicit `enum`: Enum[E]): Validator.Enumeration[E] = + Validator.enumeration(`enum`.values.toList, v => Some(v.entryName), Some(SName(fullName(`enum`)))) - implicit def schemaForEnumEntry[E <: EnumEntry](implicit annotations: SchemaAnnotations[E], enum: Enum[E]): Schema[E] = + implicit def schemaForEnumEntry[E <: EnumEntry](implicit annotations: SchemaAnnotations[E], `enum`: Enum[E]): Schema[E] = annotations.enrich(Schema[E](SchemaType.SString()).validate(validatorEnumEntry)) - def plainCodecEnumEntryUsing[E <: EnumEntry](f: String => Option[E])(implicit enum: Enum[E]): Codec[String, E, CodecFormat.TextPlain] = { + def plainCodecEnumEntryUsing[E <: EnumEntry](f: String => Option[E])(implicit `enum`: Enum[E]): Codec[String, E, CodecFormat.TextPlain] = { val validator = validatorEnumEntry Codec.string .mapDecode { s => @@ -25,64 +25,64 @@ trait TapirCodecEnumeratum { .validate(validatorEnumEntry) } - def plainCodecEnumEntryDecodeCaseInsensitive[E <: EnumEntry](implicit enum: Enum[E]): Codec.PlainCodec[E] = plainCodecEnumEntryUsing( - enum.withNameInsensitiveOption + def plainCodecEnumEntryDecodeCaseInsensitive[E <: EnumEntry](implicit `enum`: Enum[E]): Codec.PlainCodec[E] = plainCodecEnumEntryUsing( + `enum`.withNameInsensitiveOption ) - implicit def plainCodecEnumEntry[E <: EnumEntry](implicit enum: Enum[E]): Codec.PlainCodec[E] = plainCodecEnumEntryUsing( - enum.withNameOption + implicit def plainCodecEnumEntry[E <: EnumEntry](implicit `enum`: Enum[E]): Codec.PlainCodec[E] = plainCodecEnumEntryUsing( + `enum`.withNameOption ) // Value enums - def validatorValueEnumEntry[T, E <: ValueEnumEntry[T]](implicit enum: ValueEnum[T, E]): Validator[E] = - Validator.enumeration(enum.values.toList, v => Some(v.value), Some(SName(fullName(`enum`)))) + def validatorValueEnumEntry[T, E <: ValueEnumEntry[T]](implicit `enum`: ValueEnum[T, E]): Validator[E] = + Validator.enumeration(`enum`.values.toList, v => Some(v.value), Some(SName(fullName(`enum`)))) - implicit def schemaForIntEnumEntry[E <: IntEnumEntry](implicit annotations: SchemaAnnotations[E], enum: IntEnum[E]): Schema[E] = + implicit def schemaForIntEnumEntry[E <: IntEnumEntry](implicit annotations: SchemaAnnotations[E], `enum`: IntEnum[E]): Schema[E] = annotations.enrich(Schema[E](SchemaType.SInteger(), format = Some("int32")).validate(validatorValueEnumEntry[Int, E])) - implicit def schemaForLongEnumEntry[E <: LongEnumEntry](implicit annotations: SchemaAnnotations[E], enum: LongEnum[E]): Schema[E] = + implicit def schemaForLongEnumEntry[E <: LongEnumEntry](implicit annotations: SchemaAnnotations[E], `enum`: LongEnum[E]): Schema[E] = annotations.enrich(Schema[E](SchemaType.SInteger(), format = Some("int64")).validate(validatorValueEnumEntry[Long, E])) - implicit def schemaForShortEnumEntry[E <: ShortEnumEntry](implicit annotations: SchemaAnnotations[E], enum: ShortEnum[E]): Schema[E] = + implicit def schemaForShortEnumEntry[E <: ShortEnumEntry](implicit annotations: SchemaAnnotations[E], `enum`: ShortEnum[E]): Schema[E] = annotations.enrich(Schema[E](SchemaType.SInteger()).validate(validatorValueEnumEntry[Short, E])) - implicit def schemaForStringEnumEntry[E <: StringEnumEntry](implicit annotations: SchemaAnnotations[E], enum: StringEnum[E]): Schema[E] = + implicit def schemaForStringEnumEntry[E <: StringEnumEntry](implicit annotations: SchemaAnnotations[E], `enum`: StringEnum[E]): Schema[E] = annotations.enrich(Schema[E](SchemaType.SString()).validate(validatorValueEnumEntry[String, E])) - implicit def schemaForByteEnumEntry[E <: ByteEnumEntry](implicit annotations: SchemaAnnotations[E], enum: ByteEnum[E]): Schema[E] = + implicit def schemaForByteEnumEntry[E <: ByteEnumEntry](implicit annotations: SchemaAnnotations[E], `enum`: ByteEnum[E]): Schema[E] = annotations.enrich(Schema[E](SchemaType.SInteger()).validate(validatorValueEnumEntry[Byte, E])) - implicit def schemaForCharEnumEntry[E <: CharEnumEntry](implicit annotations: SchemaAnnotations[E], enum: CharEnum[E]): Schema[E] = + implicit def schemaForCharEnumEntry[E <: CharEnumEntry](implicit annotations: SchemaAnnotations[E], `enum`: CharEnum[E]): Schema[E] = annotations.enrich(Schema[E](SchemaType.SString()).validate(validatorValueEnumEntry[Char, E])) def plainCodecValueEnumEntry[T, E <: ValueEnumEntry[T]](implicit - enum: ValueEnum[T, E], + `enum`: ValueEnum[T, E], baseCodec: Codec.PlainCodec[T], schema: Schema[E] ): Codec.PlainCodec[E] = baseCodec .mapDecode { v => - enum + `enum` .withValueOpt(v) .map(DecodeResult.Value(_)) - .getOrElse(DecodeResult.Mismatch(s"One of: ${enum.values.map(_.value).mkString(", ")}", v.toString)) + .getOrElse(DecodeResult.Mismatch(s"One of: ${`enum`.values.map(_.value).mkString(", ")}", v.toString)) }(_.value) .schema(schema) - implicit def plainCodecIntEnumEntry[E <: IntEnumEntry](implicit enum: IntEnum[E]): Codec.PlainCodec[E] = + implicit def plainCodecIntEnumEntry[E <: IntEnumEntry](implicit `enum`: IntEnum[E]): Codec.PlainCodec[E] = plainCodecValueEnumEntry[Int, E] - implicit def plainCodecLongEnumEntry[E <: LongEnumEntry](implicit enum: LongEnum[E]): Codec.PlainCodec[E] = + implicit def plainCodecLongEnumEntry[E <: LongEnumEntry](implicit `enum`: LongEnum[E]): Codec.PlainCodec[E] = plainCodecValueEnumEntry[Long, E] - implicit def plainCodecShortEnumEntry[E <: ShortEnumEntry](implicit enum: ShortEnum[E]): Codec.PlainCodec[E] = + implicit def plainCodecShortEnumEntry[E <: ShortEnumEntry](implicit `enum`: ShortEnum[E]): Codec.PlainCodec[E] = plainCodecValueEnumEntry[Short, E] - implicit def plainCodecStringEnumEntry[E <: StringEnumEntry](implicit enum: StringEnum[E]): Codec.PlainCodec[E] = + implicit def plainCodecStringEnumEntry[E <: StringEnumEntry](implicit `enum`: StringEnum[E]): Codec.PlainCodec[E] = plainCodecValueEnumEntry[String, E] - implicit def plainCodecByteEnumEntry[E <: ByteEnumEntry](implicit enum: ByteEnum[E]): Codec.PlainCodec[E] = + implicit def plainCodecByteEnumEntry[E <: ByteEnumEntry](implicit `enum`: ByteEnum[E]): Codec.PlainCodec[E] = plainCodecValueEnumEntry[Byte, E] private def fullName[T](t: T) = t.getClass.getName.replace("$", ".") diff --git a/integrations/enumeratum/src/test/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratumTest.scala b/integrations/enumeratum/src/test/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratumTest.scala index cd15ee361b..7bee203cf4 100644 --- a/integrations/enumeratum/src/test/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratumTest.scala +++ b/integrations/enumeratum/src/test/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratumTest.scala @@ -51,8 +51,8 @@ class TapirCodecEnumeratumTest extends AnyFlatSpec with Matchers { testValueEnumValidator[Char, TestCharEnumEntry, CharEnum[TestCharEnumEntry]](implicitly[Schema[TestCharEnumEntry]].validator) } - private def testEnumValidator[E <: EnumEntry](validator: Validator[E])(implicit enum: Enum[E]): Unit = { - enum.values.foreach { v => + private def testEnumValidator[E <: EnumEntry](validator: Validator[E])(implicit `enum`: Enum[E]): Unit = { + `enum`.values.foreach { v => validator(v) shouldBe Nil validator match { case Validator.Enumeration(_, Some(encode), name) => @@ -64,9 +64,9 @@ class TapirCodecEnumeratumTest extends AnyFlatSpec with Matchers { } private def testValueEnumValidator[T, EE <: ValueEnumEntry[T], E <: ValueEnum[T, EE]](validator: Validator[EE])(implicit - enum: E + `enum`: E ): Unit = { - enum.values.foreach { v => + `enum`.values.foreach { v => validator(v) shouldBe Nil validator match { case Validator.Enumeration(_, Some(encode), name) => @@ -88,15 +88,15 @@ class TapirCodecEnumeratumTest extends AnyFlatSpec with Matchers { testValueEnumPlainCodec[Byte, TestByteEnumEntry, ByteEnum[TestByteEnumEntry]](implicitly[PlainCodec[TestByteEnumEntry]]) } - private def testEnumPlainCodec[E <: EnumEntry](codec: PlainCodec[E])(implicit enum: Enum[E]): Unit = { - enum.values.foreach { v => + private def testEnumPlainCodec[E <: EnumEntry](codec: PlainCodec[E])(implicit `enum`: Enum[E]): Unit = { + `enum`.values.foreach { v => codec.encode(v) shouldBe v.entryName codec.decode(v.entryName) shouldBe DecodeResult.Value(v) } } - private def testValueEnumPlainCodec[T, EE <: ValueEnumEntry[T], E <: ValueEnum[T, EE]](codec: PlainCodec[EE])(implicit enum: E): Unit = { - enum.values.foreach { v => + private def testValueEnumPlainCodec[T, EE <: ValueEnumEntry[T], E <: ValueEnum[T, EE]](codec: PlainCodec[EE])(implicit `enum`: E): Unit = { + `enum`.values.foreach { v => codec.encode(v) shouldBe v.value.toString codec.decode(v.value.toString) shouldBe DecodeResult.Value(v) } @@ -112,8 +112,10 @@ class TapirCodecEnumeratumTest extends AnyFlatSpec with Matchers { it should "create schema with custom discriminator based on enumeratum enum" in { // given sealed trait OfferType extends EnumEntry with Snakecase - object OfferType { + object OfferType extends Enum[OfferType] { case object OfferOne extends OfferType + + override def values: scala.collection.immutable.IndexedSeq[OfferType] = findValues } sealed trait CreateOfferRequest { From 9f542ce6036ba00d1497ba6527c6b857e8249899 Mon Sep 17 00:00:00 2001 From: Maciej Buszka Date: Tue, 10 Jan 2023 13:08:00 +0100 Subject: [PATCH 085/120] Scope `-Yretain-trees` to test suite only --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ccde8f1d92..57a900da4b 100644 --- a/build.sbt +++ b/build.sbt @@ -528,7 +528,7 @@ lazy val enumeratum: ProjectMatrix = (projectMatrix in file("integrations/enumer "com.beachape" %%% "enumeratum" % Versions.enumeratum, scalaTest.value % Test ), - scalacOptions ++= { + Test/scalacOptions ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((3, _)) => Seq("-Yretain-trees") case _ => Seq() From 08d55f47d2b2a82011d2434d52b13633ae1f4a4c Mon Sep 17 00:00:00 2001 From: adamw Date: Tue, 10 Jan 2023 15:20:35 +0100 Subject: [PATCH 086/120] Schemas for any / any object --- core/src/main/scala/sttp/tapir/Schema.scala | 12 ++++++++++++ .../scala/sttp/tapir/json/circe/TapirJsonCirce.scala | 10 ++-------- .../scala/sttp/tapir/json/json4s/TapirJson4s.scala | 7 +------ .../scala/sttp/tapir/json/play/TapirJsonPlay.scala | 11 ++--------- .../scala/sttp/tapir/json/spray/TapirJsonSpray.scala | 11 ++--------- .../scala/sttp/tapir/json/zio/TapirJsonZio.scala | 11 ++--------- .../scala/sttp/tapir/json/zio/TapirJsonZio.scala | 11 ++--------- 7 files changed, 23 insertions(+), 50 deletions(-) diff --git a/core/src/main/scala/sttp/tapir/Schema.scala b/core/src/main/scala/sttp/tapir/Schema.scala index d54ba20da4..cbb05ba81f 100644 --- a/core/src/main/scala/sttp/tapir/Schema.scala +++ b/core/src/main/scala/sttp/tapir/Schema.scala @@ -377,6 +377,18 @@ object Schema extends LowPrioritySchema with SchemaCompanionMacros { */ def wrapWithSingleFieldProduct[T](schema: Schema[T])(implicit conf: Configuration): Schema[T] = wrapWithSingleFieldProduct(schema, FieldName(conf.toDiscriminatorValue(schema.name.getOrElse(SName.Unit)))) + + /** A schema allowing anything: a number, string, object, etc. A [[SCoproduct]] with no specified subtypes. + * @see + * [[anyObject]] + */ + def any[T]: Schema[T] = Schema(SCoproduct(Nil, None)(_ => None), None) + + /** A schema allowing any object. A [[SProduct]] with no specified fields. + * @see + * [[any]] + */ + def anyObject[T]: Schema[T] = Schema(SProduct(Nil), None) } trait LowPrioritySchema { diff --git a/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala b/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala index c99af83b30..12063c2789 100644 --- a/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala +++ b/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala @@ -45,12 +45,6 @@ trait TapirJsonCirce { def jsonPrinter: Printer = Printer.noSpaces - // Json is a coproduct with unknown implementations - implicit val schemaForCirceJson: Schema[Json] = - Schema( - SCoproduct(Nil, None)(_ => None), - None - ) - - implicit val schemaForCirceJsonObject: Schema[JsonObject] = Schema(SProduct(Nil), Some(SName("io.circe.JsonObject"))) + implicit val schemaForCirceJson: Schema[Json] = Schema.any + implicit val schemaForCirceJsonObject: Schema[JsonObject] = Schema.anyObject[JsonObject].name(SName("io.circe.JsonObject")) } diff --git a/json/json4s/src/main/scala/sttp/tapir/json/json4s/TapirJson4s.scala b/json/json4s/src/main/scala/sttp/tapir/json/json4s/TapirJson4s.scala index 1a5a7b88cb..220ba87cfe 100644 --- a/json/json4s/src/main/scala/sttp/tapir/json/json4s/TapirJson4s.scala +++ b/json/json4s/src/main/scala/sttp/tapir/json/json4s/TapirJson4s.scala @@ -31,10 +31,5 @@ trait TapirJson4s { serialization.write(t.asInstanceOf[AnyRef]) } - // JValue is a coproduct with unknown implementations - implicit val schemaForJson4s: Schema[JValue] = - Schema( - SCoproduct(Nil, None)(_ => None), - None - ) + implicit val schemaForJson4s: Schema[JValue] = Schema.any } diff --git a/json/playjson/src/main/scala/sttp/tapir/json/play/TapirJsonPlay.scala b/json/playjson/src/main/scala/sttp/tapir/json/play/TapirJsonPlay.scala index b27e5834b0..de9ae1e9e3 100644 --- a/json/playjson/src/main/scala/sttp/tapir/json/play/TapirJsonPlay.scala +++ b/json/playjson/src/main/scala/sttp/tapir/json/play/TapirJsonPlay.scala @@ -44,13 +44,6 @@ trait TapirJsonPlay { } } { t => Json.stringify(Json.toJson(t)) } - // JsValue is a coproduct with unknown implementations - implicit val schemaForPlayJsValue: Schema[JsValue] = - Schema( - SCoproduct(Nil, None)(_ => None), - None - ) - - implicit val schemaForPlayJsObject: Schema[JsObject] = - Schema(SProduct(Nil), Some(SName("play.api.libs.json.JsObject"))) + implicit val schemaForPlayJsValue: Schema[JsValue] = Schema.any + implicit val schemaForPlayJsObject: Schema[JsObject] = Schema.anyObject[JsObject].name(SName("play.api.libs.json.JsObject")) } diff --git a/json/sprayjson/src/main/scala/sttp/tapir/json/spray/TapirJsonSpray.scala b/json/sprayjson/src/main/scala/sttp/tapir/json/spray/TapirJsonSpray.scala index c2dd9f936b..e7368e972f 100644 --- a/json/sprayjson/src/main/scala/sttp/tapir/json/spray/TapirJsonSpray.scala +++ b/json/sprayjson/src/main/scala/sttp/tapir/json/spray/TapirJsonSpray.scala @@ -32,13 +32,6 @@ trait TapirJsonSpray { } } { t => t.toJson.toString } - // JsValue is a coproduct with unknown implementations - implicit val schemaForSprayJsValue: Schema[JsValue] = - Schema( - SCoproduct(Nil, None)(_ => None), - None - ) - - implicit val schemaForSprayJsObject: Schema[JsObject] = - Schema(SProduct(Nil), Some(SName("spray.json.JsObject"))) + implicit val schemaForSprayJsValue: Schema[JsValue] = Schema.any + implicit val schemaForSprayJsObject: Schema[JsObject] = Schema.anyObject[JsObject].name(SName("spray.json.JsObject")) } diff --git a/json/zio/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala b/json/zio/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala index e9ca6ec439..213b33485d 100644 --- a/json/zio/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala +++ b/json/zio/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala @@ -42,13 +42,6 @@ trait TapirJsonZio { } } - // JsValue is a coproduct with unknown implementations - implicit val schemaForZioJsonValue: Schema[Json] = - Schema( - SCoproduct(Nil, None)(_ => None), - None - ) - - implicit val schemaForZioJsonObject: Schema[Obj] = - Schema(SProduct(Nil), Some(SName("zio.json.ast.Json.Obj"))) + implicit val schemaForZioJsonValue: Schema[Json] = Schema.any + implicit val schemaForZioJsonObject: Schema[Obj] = Schema.anyObject[Obj].name(SName("zio.json.ast.Json.Obj")) } diff --git a/json/zio1/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala b/json/zio1/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala index e9ca6ec439..213b33485d 100644 --- a/json/zio1/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala +++ b/json/zio1/src/main/scala/sttp/tapir/json/zio/TapirJsonZio.scala @@ -42,13 +42,6 @@ trait TapirJsonZio { } } - // JsValue is a coproduct with unknown implementations - implicit val schemaForZioJsonValue: Schema[Json] = - Schema( - SCoproduct(Nil, None)(_ => None), - None - ) - - implicit val schemaForZioJsonObject: Schema[Obj] = - Schema(SProduct(Nil), Some(SName("zio.json.ast.Json.Obj"))) + implicit val schemaForZioJsonValue: Schema[Json] = Schema.any + implicit val schemaForZioJsonObject: Schema[Obj] = Schema.anyObject[Obj].name(SName("zio.json.ast.Json.Obj")) } From 21e85453c34c1ed92fb2da5cc9b8cd276cf33ffd Mon Sep 17 00:00:00 2001 From: adamw Date: Tue, 10 Jan 2023 15:40:06 +0100 Subject: [PATCH 087/120] Mention that adopt-tapir projects include a test --- doc/examples.md | 2 +- doc/testing.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/examples.md b/doc/examples.md index 93967c360d..31112afd98 100644 --- a/doc/examples.md +++ b/doc/examples.md @@ -4,7 +4,7 @@ The [examples](https://github.com/softwaremill/tapir/tree/master/examples/src/ma ## Generate a tapir project -You can generate a simple project using tapir using [adopt-tapir](https://adopt-tapir.softwaremill.com). +You can generate a simple tapir-based project using chosen features, build tool and effect system using [adopt-tapir](https://adopt-tapir.softwaremill.com). ## Third-party examples diff --git a/doc/testing.md b/doc/testing.md index b75af08d63..d5e83d5921 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -80,6 +80,8 @@ class MySpec extends AsyncFlatSpec with Matchers { The `.backend` method creates the enriched `SttpBackendStub`, using the provided server endpoints and their behaviors. Any requests will be handled by a stub server interpreter, using the complete request handling logic. +Projects generated using [adopt-tapir](https://adopt-tapir.softwaremill.com) include a test which uses the above approach. + ### Custom interpreters Custom interpreters can be provided to the stub. For example, to test custom exception handling, we might have the From cbcc49175aae890bbe7539fd38e42668ee1bcc29 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Wed, 11 Jan 2023 00:16:12 +0000 Subject: [PATCH 088/120] Update client3:akka-http-backend, ... to 3.8.8 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index f32f12f653..12cf90b5b8 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -6,7 +6,7 @@ object Versions { val circe = "0.14.3" val circeGenericExtras = "0.14.3" val circeYaml = "0.14.2" - val sttp = "3.8.7" + val sttp = "3.8.8" val sttpModel = "1.5.4" val sttpShared = "1.3.12" val sttpApispec = "0.3.1" From da741c9aaa6702bd3043f6d8a348f13c511e7b98 Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 11 Jan 2023 09:34:30 +0100 Subject: [PATCH 089/120] Update sttp-apispec, add a test for multi-value schema example --- ...expected_schema_example_multiple_value.yml | 37 +++++++++++++++++++ .../docs/openapi/VerifyYamlExampleTest.scala | 14 +++++++ project/Versions.scala | 2 +- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 docs/openapi-docs/src/test/resources/example/expected_schema_example_multiple_value.yml diff --git a/docs/openapi-docs/src/test/resources/example/expected_schema_example_multiple_value.yml b/docs/openapi-docs/src/test/resources/example/expected_schema_example_multiple_value.yml new file mode 100644 index 0000000000..bfa3ccb672 --- /dev/null +++ b/docs/openapi-docs/src/test/resources/example/expected_schema_example_multiple_value.yml @@ -0,0 +1,37 @@ +openapi: 3.0.3 +info: + title: Examples + version: '1.0' +paths: + /: + post: + operationId: postRoot + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ContainsList' + required: true + responses: + '200': + description: '' + '400': + description: 'Invalid value for: body' + content: + text/plain: + schema: + type: string +components: + schemas: + ContainsList: + type: object + properties: + l: + type: array + items: + type: integer + format: int32 + example: + - 1 + - 2 + - 3 \ No newline at end of file diff --git a/docs/openapi-docs/src/test/scalajvm/sttp/tapir/docs/openapi/VerifyYamlExampleTest.scala b/docs/openapi-docs/src/test/scalajvm/sttp/tapir/docs/openapi/VerifyYamlExampleTest.scala index cdd2223b4f..7625797f50 100644 --- a/docs/openapi-docs/src/test/scalajvm/sttp/tapir/docs/openapi/VerifyYamlExampleTest.scala +++ b/docs/openapi-docs/src/test/scalajvm/sttp/tapir/docs/openapi/VerifyYamlExampleTest.scala @@ -152,6 +152,20 @@ class VerifyYamlExampleTest extends AnyFunSuite with Matchers { actualYamlNoIndent shouldBe expectedYaml } + test("support schema examples with multiple values") { // https://github.com/softwaremill/sttp-apispec/issues/49 + val expectedYaml = load("example/expected_schema_example_multiple_value.yml") + + implicit val testSchema: Schema[List[Int]] = Schema.schemaForInt.asIterable[List].encodedExample(List(1, 2, 3)) + case class ContainsList(l: List[Int]) + + val e = endpoint.post.in(jsonBody[ContainsList]) + + val actualYaml = OpenAPIDocsInterpreter().toOpenAPI(e, Info("Examples", "1.0")).toYaml + val actualYamlNoIndent = noIndentation(actualYaml) + + actualYamlNoIndent shouldBe expectedYaml + } + test("should support encoded examples for streaming bodies") { val expectedYaml = load("example/expected_stream_example.yml") diff --git a/project/Versions.scala b/project/Versions.scala index 67131b8409..c94b3afac6 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -9,7 +9,7 @@ object Versions { val sttp = "3.8.7" val sttpModel = "1.5.4" val sttpShared = "1.3.12" - val sttpApispec = "0.3.1" + val sttpApispec = "0.3.2" val akkaHttp = "10.2.10" val akkaStreams = "2.6.20" val swaggerUi = "4.15.5" From 113587b1776456c283958aaf9e72dea9aaebedbf Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 11 Jan 2023 10:51:13 +0100 Subject: [PATCH 090/120] Remove the runtime implicit parameter from the netty-zio server interpreter --- .../server/netty/zio/NettyZioServer.scala | 53 +++++++++---------- .../netty/zio/NettyZioServerInterpreter.scala | 4 +- .../zio/NettyZioTestServerInterpreter.scala | 10 ++-- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala index eaa75d1f91..69f03ede2a 100644 --- a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala @@ -7,14 +7,12 @@ import sttp.tapir.server.netty.Route import sttp.tapir.server.netty.internal.{NettyBootstrap, NettyServerHandler} import sttp.tapir.server.netty.zio.internal.ZioUtil.{nettyChannelFutureToScala, nettyFutureToScala} import sttp.tapir.ztapir.{RIOMonadError, ZServerEndpoint} -import zio.{RIO, Runtime, Unsafe, ZIO} +import zio.{RIO, Unsafe, ZIO} import java.net.{InetSocketAddress, SocketAddress} import java.nio.file.Path -case class NettyZioServer[R, SA <: SocketAddress](routes: Vector[Route[RIO[R, *]]], options: NettyZioServerOptions[R, SA])(implicit - runtime: Runtime[R] -) { +case class NettyZioServer[R, SA <: SocketAddress](routes: Vector[RIO[R, Route[RIO[R, *]]]], options: NettyZioServerOptions[R, SA]) { def addEndpoint(se: ZServerEndpoint[R, Any]): NettyZioServer[R, SA] = addEndpoints(List(se)) def addEndpoint( se: ZServerEndpoint[R, Any], @@ -31,8 +29,10 @@ case class NettyZioServer[R, SA <: SocketAddress](routes: Vector[Route[RIO[R, *] NettyZioServerInterpreter[R](overrideOptions).toRoute(ses) ) - def addRoute(r: Route[RIO[R, *]]): NettyZioServer[R, SA] = copy(routes = routes :+ r) - def addRoutes(r: Iterable[Route[RIO[R, *]]]): NettyZioServer[R, SA] = copy(routes = routes ++ r) + def addRoute(r: Route[RIO[R, *]]): NettyZioServer[R, SA] = addRoute(ZIO.succeed(r)) + + def addRoute(r: RIO[R, Route[RIO[R, *]]]): NettyZioServer[R, SA] = copy(routes = routes :+ r) + def addRoutes(r: Iterable[RIO[R, Route[RIO[R, *]]]]): NettyZioServer[R, SA] = copy(routes = routes ++ r) def options[SA2 <: SocketAddress](o: NettyZioServerOptions[R, SA2]): NettyZioServer[R, SA2] = copy(options = o) @@ -51,27 +51,30 @@ case class NettyZioServer[R, SA <: SocketAddress](routes: Vector[Route[RIO[R, *] options(options.nettyOptions(nettyOptions)) } - def start(): RIO[R, NettyZioServerBinding[R, SA]] = ZIO.suspend { - val eventLoopGroup = options.nettyOptions.eventLoopConfig.initEventLoopGroup() - implicit val monadError: RIOMonadError[R] = new RIOMonadError[R] - val route: Route[RIO[R, *]] = Route.combine(routes) - - val channelFuture = NettyBootstrap[RIO[R, *]]( - options.nettyOptions, - new NettyServerHandler[RIO[R, *]]( - route, - (f: () => RIO[R, Unit]) => Unsafe.unsafe(implicit u => runtime.unsafe.runToFuture(f())) - ), - eventLoopGroup - ) + def start(): RIO[R, NettyZioServerBinding[R, SA]] = for { + runtime <- ZIO.runtime[R] + routes <- ZIO.foreach(routes)(identity) + eventLoopGroup = options.nettyOptions.eventLoopConfig.initEventLoopGroup() + channelFuture = { + implicit val monadError: RIOMonadError[R] = new RIOMonadError[R] + val route: Route[RIO[R, *]] = Route.combine(routes) - nettyChannelFutureToScala(channelFuture).map(ch => + NettyBootstrap[RIO[R, *]]( + options.nettyOptions, + new NettyServerHandler[RIO[R, *]]( + route, + (f: () => RIO[R, Unit]) => Unsafe.unsafe(implicit u => runtime.unsafe.runToFuture(f())) + ), + eventLoopGroup + ) + } + binding <- nettyChannelFutureToScala(channelFuture).map(ch => NettyZioServerBinding( ch.localAddress().asInstanceOf[SA], () => stop(ch, eventLoopGroup) ) ) - } + } yield binding private def stop(ch: Channel, eventLoopGroup: EventLoopGroup): RIO[R, Unit] = { ZIO.suspend { @@ -85,14 +88,10 @@ case class NettyZioServer[R, SA <: SocketAddress](routes: Vector[Route[RIO[R, *] } object NettyZioServer { - def apply[R](implicit - runtime: Runtime[R] - ): NettyZioServer[R, InetSocketAddress] = + def apply[R]: NettyZioServer[R, InetSocketAddress] = apply(NettyZioServerOptions.default[R]) - def apply[R, SA <: SocketAddress](options: NettyZioServerOptions[R, SA])(implicit - runtime: Runtime[R] - ): NettyZioServer[R, SA] = + def apply[R, SA <: SocketAddress](options: NettyZioServerOptions[R, SA]): NettyZioServer[R, SA] = NettyZioServer(Vector.empty, options) } diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala index bdce856c60..ec3c638396 100644 --- a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala @@ -4,12 +4,12 @@ import sttp.tapir.server.netty.Route import sttp.tapir.server.netty.internal.{NettyServerInterpreter, RunAsync} import sttp.tapir.server.netty.zio.NettyZioServerInterpreter.ZioRunAsync import sttp.tapir.ztapir.{RIOMonadError, ZServerEndpoint} -import zio.{CancelableFuture, RIO, Runtime, Unsafe} +import zio.{CancelableFuture, RIO, Runtime, Unsafe, ZIO} trait NettyZioServerInterpreter[R] { def nettyServerOptions: NettyZioServerOptions[R, _] - def toRoute(ses: List[ZServerEndpoint[R, Any]])(implicit runtime: Runtime[R]): Route[RIO[R, *]] = { + def toRoute(ses: List[ZServerEndpoint[R, Any]]): RIO[R, Route[RIO[R, *]]] = ZIO.runtime.map { runtime: Runtime[R] => implicit val monadError: RIOMonadError[R] = new RIOMonadError[R] val runAsync = new ZioRunAsync(runtime) NettyServerInterpreter diff --git a/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioTestServerInterpreter.scala b/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioTestServerInterpreter.scala index 1aae4bc148..fee75b7d06 100644 --- a/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioTestServerInterpreter.scala +++ b/server/netty-server/zio/src/test/scala/sttp/tapir/server/netty/zio/NettyZioTestServerInterpreter.scala @@ -12,22 +12,22 @@ import zio.{CancelableFuture, Runtime, Task, Unsafe} import java.net.InetSocketAddress class NettyZioTestServerInterpreter[R](eventLoopGroup: NioEventLoopGroup) - extends TestServerInterpreter[Task, Any, NettyZioServerOptions[Any, InetSocketAddress], Route[Task]] { - implicit val runtime: Runtime[R] = Runtime.default.asInstanceOf[Runtime[R]] - - override def route(es: List[ServerEndpoint[Any, Task]], interceptors: Interceptors): Route[Task] = { + extends TestServerInterpreter[Task, Any, NettyZioServerOptions[Any, InetSocketAddress], Task[Route[Task]]] { + override def route(es: List[ServerEndpoint[Any, Task]], interceptors: Interceptors): Task[Route[Task]] = { val serverOptions: NettyZioServerOptions[Any, InetSocketAddress] = interceptors( NettyZioServerOptions.customiseInterceptors ).options NettyZioServerInterpreter(serverOptions).toRoute(es) } - override def server(routes: NonEmptyList[Route[Task]]): Resource[IO, Port] = { + override def server(routes: NonEmptyList[Task[Route[Task]]]): Resource[IO, Port] = { val options = NettyZioServerOptions .default[R] .nettyOptions(NettyOptions.default.eventLoopGroup(eventLoopGroup).randomPort.noShutdownOnClose) + val runtime: Runtime[R] = Runtime.default.asInstanceOf[Runtime[R]] + val server: CancelableFuture[NettyZioServerBinding[R, InetSocketAddress]] = Unsafe.unsafe(implicit u => runtime.unsafe.runToFuture(NettyZioServer(options).addRoutes(routes.toList).start())) From 6200cc29e3ac20281b986470c5aa856aa5067baf Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 11 Jan 2023 10:51:45 +0100 Subject: [PATCH 091/120] Add a method to quickly create a request interceptor, which runs an effect --- .../tapir/server/interceptor/Interceptor.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/server/core/src/main/scala/sttp/tapir/server/interceptor/Interceptor.scala b/server/core/src/main/scala/sttp/tapir/server/interceptor/Interceptor.scala index ac80f4646b..a9a57aeb28 100644 --- a/server/core/src/main/scala/sttp/tapir/server/interceptor/Interceptor.scala +++ b/server/core/src/main/scala/sttp/tapir/server/interceptor/Interceptor.scala @@ -83,6 +83,20 @@ object RequestInterceptor { } } } + + /** Run an effect when a request is received. */ + def effect[F[_]](f: ServerRequest => F[Unit]): RequestInterceptor[F] = new RequestInterceptor[F] { + override def apply[R, B]( + responder: Responder[F, B], + requestHandler: EndpointInterceptor[F] => RequestHandler[F, R, B] + ): RequestHandler[F, R, B] = + new RequestHandler[F, R, B] { + override def apply(request: ServerRequest, endpoints: List[ServerEndpoint[R, F]])(implicit + monad: MonadError[F] + ): F[RequestResult[B]] = + f(request).flatMap(_ => requestHandler(EndpointInterceptor.noop)(request, endpoints)) + } + } } /** Allows intercepting the handling of a request by an endpoint, when either the endpoint's inputs have been decoded successfully, or when From 50dbaec4207be676f0f3673746eda9f9f9f0f1e3 Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 11 Jan 2023 11:21:58 +0100 Subject: [PATCH 092/120] Add a zio logging + correlation id + netty server example & helper methods to RequestInterceptor --- build.sbt | 1 + ...oLoggingWithCorrelationIdNettyServer.scala | 43 +++++++++++++++++++ .../server/interceptor/Interceptor.scala | 40 +++++++++++++++++ .../server/netty/zio/NettyZioServer.scala | 2 +- 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 examples/src/main/scala/sttp/tapir/examples/logging/ZioLoggingWithCorrelationIdNettyServer.scala diff --git a/build.sbt b/build.sbt index f4c167fead..bbf9959598 100644 --- a/build.sbt +++ b/build.sbt @@ -1713,6 +1713,7 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples")) zioHttpServer, nettyServer, nettyServerCats, + nettyServerZio, sttpStubServer, playJson, prometheusMetrics, diff --git a/examples/src/main/scala/sttp/tapir/examples/logging/ZioLoggingWithCorrelationIdNettyServer.scala b/examples/src/main/scala/sttp/tapir/examples/logging/ZioLoggingWithCorrelationIdNettyServer.scala new file mode 100644 index 0000000000..8aec28f32d --- /dev/null +++ b/examples/src/main/scala/sttp/tapir/examples/logging/ZioLoggingWithCorrelationIdNettyServer.scala @@ -0,0 +1,43 @@ +package sttp.tapir.examples.logging + +import sttp.client3.httpclient.zio.HttpClientZioBackend +import sttp.client3.{UriContext, basicRequest} +import sttp.tapir.model.ServerRequest +import sttp.tapir.server.interceptor.{RequestInterceptor, RequestResult} +import sttp.tapir.server.interceptor.RequestInterceptor.RequestResultEffectTransform +import sttp.tapir.server.netty.zio.{NettyZioServer, NettyZioServerOptions} +import sttp.tapir.ztapir._ +import zio.{ExitCode, Task, URIO, ZIO, ZIOAppDefault, durationInt} + +object ZioLoggingWithCorrelationIdNettyServer extends ZIOAppDefault { + val CorrelationIdHeader = "X-Correlation-Id" + + // An endpoint with some logging added + val loggingEndpoint: ZServerEndpoint[Any, Any] = + endpoint.get.in("hello").in(query[String]("name")).zServerLogic { name => + for { + _ <- ZIO.log(s"Starting computation for: $name ...") + fiber <- (ZIO.sleep(100.millis) *> ZIO.log(s"Saying hello to: $name")).fork + _ <- ZIO.log(s"Bye, $name!") + _ <- fiber.join + } yield () + } + + val correlationIdInterceptor = RequestInterceptor.transformResultEffect(new RequestResultEffectTransform[Task] { + override def apply[B](request: ServerRequest, result: Task[RequestResult[B]]): Task[RequestResult[B]] = { + val cid = request.header(CorrelationIdHeader).getOrElse("NO-CID") + ZIO.logAnnotate("cid", cid)(result) + } + }) + + override def run: URIO[Any, ExitCode] = { + val serverOptions = NettyZioServerOptions.customiseInterceptors.prependInterceptor(correlationIdInterceptor).options + (for { + binding <- NettyZioServer(serverOptions).port(8080).addEndpoint(loggingEndpoint).start() + httpClient <- HttpClientZioBackend() + _ <- httpClient.send(basicRequest.get(uri"http://localhost:8080/hello?name=Emma").header(CorrelationIdHeader, "ABC-123")) + _ <- httpClient.send(basicRequest.get(uri"http://localhost:8080/hello?name=Bob")) + _ <- binding.stop() + } yield ()).exitCode + } +} diff --git a/server/core/src/main/scala/sttp/tapir/server/interceptor/Interceptor.scala b/server/core/src/main/scala/sttp/tapir/server/interceptor/Interceptor.scala index a9a57aeb28..b576979dc1 100644 --- a/server/core/src/main/scala/sttp/tapir/server/interceptor/Interceptor.scala +++ b/server/core/src/main/scala/sttp/tapir/server/interceptor/Interceptor.scala @@ -63,6 +63,46 @@ object RequestInterceptor { } } + trait RequestResultTransform[F[_]] { + def apply[B](request: ServerRequest, result: RequestResult[B]): F[RequestResult[B]] + } + + /** Create a request interceptor which transforms the result, which might be either a response, or a list of endpoint decoding failures. + */ + def transformResult[F[_]](f: RequestResultTransform[F]): RequestInterceptor[F] = new RequestInterceptor[F] { + override def apply[R, B]( + responder: Responder[F, B], + requestHandler: EndpointInterceptor[F] => RequestHandler[F, R, B] + ): RequestHandler[F, R, B] = + new RequestHandler[F, R, B] { + override def apply(request: ServerRequest, endpoints: List[ServerEndpoint[R, F]])(implicit + monad: MonadError[F] + ): F[RequestResult[B]] = + requestHandler(EndpointInterceptor.noop)(request, endpoints).flatMap(f(request, _)) + } + } + + trait RequestResultEffectTransform[F[_]] { + def apply[B](request: ServerRequest, result: F[RequestResult[B]]): F[RequestResult[B]] + } + + /** Create a request interceptor which transforms the *effect* which computes the result (either a response, or a list of endpoint + * decoding failures), that is the `F[RequestResult[B]]` value. To transform the result itself, it might be easier to use + * [[transformResult]]. + */ + def transformResultEffect[F[_]](f: RequestResultEffectTransform[F]): RequestInterceptor[F] = new RequestInterceptor[F] { + override def apply[R, B]( + responder: Responder[F, B], + requestHandler: EndpointInterceptor[F] => RequestHandler[F, R, B] + ): RequestHandler[F, R, B] = + new RequestHandler[F, R, B] { + override def apply(request: ServerRequest, endpoints: List[ServerEndpoint[R, F]])(implicit + monad: MonadError[F] + ): F[RequestResult[B]] = + f(request, requestHandler(EndpointInterceptor.noop)(request, endpoints)) + } + } + trait ServerEndpointFilter[F[_]] { def apply[R](endpoints: List[ServerEndpoint[R, F]]): F[List[ServerEndpoint[R, F]]] } diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala index 69f03ede2a..3b8645e210 100644 --- a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServer.scala @@ -88,7 +88,7 @@ case class NettyZioServer[R, SA <: SocketAddress](routes: Vector[RIO[R, Route[RI } object NettyZioServer { - def apply[R]: NettyZioServer[R, InetSocketAddress] = + def apply[R](): NettyZioServer[R, InetSocketAddress] = apply(NettyZioServerOptions.default[R]) def apply[R, SA <: SocketAddress](options: NettyZioServerOptions[R, SA]): NettyZioServer[R, SA] = From 55dee0ee407d3ce1a67bfd2c5b51e1a3d0a3e6d5 Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 11 Jan 2023 12:13:48 +0100 Subject: [PATCH 093/120] Fix scala3 compile --- .../sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala index ec3c638396..f7b0485d36 100644 --- a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerInterpreter.scala @@ -9,7 +9,7 @@ import zio.{CancelableFuture, RIO, Runtime, Unsafe, ZIO} trait NettyZioServerInterpreter[R] { def nettyServerOptions: NettyZioServerOptions[R, _] - def toRoute(ses: List[ZServerEndpoint[R, Any]]): RIO[R, Route[RIO[R, *]]] = ZIO.runtime.map { runtime: Runtime[R] => + def toRoute(ses: List[ZServerEndpoint[R, Any]]): RIO[R, Route[RIO[R, *]]] = ZIO.runtime.map { (runtime: Runtime[R]) => implicit val monadError: RIOMonadError[R] = new RIOMonadError[R] val runAsync = new ZioRunAsync(runtime) NettyServerInterpreter From 2add9cbec1fb60f133811fbcee1139af466b13ff Mon Sep 17 00:00:00 2001 From: scala-steward Date: Fri, 13 Jan 2023 00:06:27 +0000 Subject: [PATCH 094/120] Update mockserver-netty to 5.15.0 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 637fd45521..4f026a7854 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -51,7 +51,7 @@ object Versions { val decline = "2.4.1" val quicklens = "1.9.0" val openTelemetry = "1.22.0" - val mockServer = "5.14.0" + val mockServer = "5.15.0" val dogstatsdClient = "4.1.0" val nettyAll = "4.1.86.Final" } From e7eca88282d5b1170e8b7d211597506cc888d833 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sat, 14 Jan 2023 00:05:25 +0000 Subject: [PATCH 095/120] Update play, play-akka-http-server, ... to 2.8.19 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 637fd45521..522263d7a8 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -33,7 +33,7 @@ object Versions { val zioInteropReactiveStreams = "2.0.0" val zioJson = "0.4.2" val playClient = "2.1.10" - val playServer = "2.8.18" + val playServer = "2.8.19" val tethys = "0.26.0" val vertx = "4.3.7" val jsScalaJavaTime = "2.5.0" From b682592be91e4454cbe0978c27a714b319046120 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Tue, 17 Jan 2023 00:05:22 +0000 Subject: [PATCH 096/120] Update zio, zio-streams, zio-test, ... to 2.0.6 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 637fd45521..8b9fda2b33 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -28,7 +28,7 @@ object Versions { val zio1InteropCats = "13.0.0.1" val zio1Json = "0.2.0" val zio1InteropReactiveStreams = "1.3.12" - val zio = "2.0.5" + val zio = "2.0.6" val zioInteropCats = "3.3.0" val zioInteropReactiveStreams = "2.0.0" val zioJson = "0.4.2" From 98d4142b30a47f71395da79cb2ec346bbad9da11 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Tue, 17 Jan 2023 00:05:33 +0000 Subject: [PATCH 097/120] Update cats-effect to 3.4.5 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 637fd45521..7ecce07f12 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -2,7 +2,7 @@ object Versions { val http4s = "0.23.17" val http4sBlazeServer = "0.23.13" val http4sBlazeClient = "0.23.13" - val catsEffect = "3.4.4" + val catsEffect = "3.4.5" val circe = "0.14.3" val circeGenericExtras = "0.14.3" val circeYaml = "0.14.2" From d6d500f23a263c7002262106ef24800d018973ff Mon Sep 17 00:00:00 2001 From: scala-steward Date: Wed, 18 Jan 2023 00:15:54 +0000 Subject: [PATCH 098/120] Update fs2-reactive-streams to 3.5.0 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 7ecce07f12..1b8072aec1 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -47,7 +47,7 @@ object Versions { val armeria = "1.21.0" val scalaJava8Compat = "1.0.2" val scalaCollectionCompat = "2.9.0" - val fs2 = "3.4.0" + val fs2 = "3.5.0" val decline = "2.4.1" val quicklens = "1.9.0" val openTelemetry = "1.22.0" From d1b3b8034a63562ac70c652fd9f21216fd51cbd9 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Wed, 18 Jan 2023 00:16:05 +0000 Subject: [PATCH 099/120] Update http4s-circe, http4s-core, ... to 0.23.18 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 7ecce07f12..efa2c32815 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -1,5 +1,5 @@ object Versions { - val http4s = "0.23.17" + val http4s = "0.23.18" val http4sBlazeServer = "0.23.13" val http4sBlazeClient = "0.23.13" val catsEffect = "3.4.5" From d6f9ebf48a7322cdfa732fccfb5780516c86275f Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 18 Jan 2023 21:07:37 +0100 Subject: [PATCH 100/120] Release 1.2.6 --- README.md | 2 +- generated-doc/out/client/http4s.md | 2 +- generated-doc/out/client/play.md | 2 +- generated-doc/out/client/sttp.md | 4 ++-- generated-doc/out/docs/asyncapi.md | 2 +- generated-doc/out/docs/openapi.md | 12 ++++++------ generated-doc/out/endpoint/integrations.md | 14 +++++++------- generated-doc/out/endpoint/json.md | 16 ++++++++-------- generated-doc/out/examples.md | 2 +- .../out/generator/sbt-openapi-codegen.md | 2 +- generated-doc/out/quickstart.md | 2 +- generated-doc/out/server/akkahttp.md | 4 ++-- generated-doc/out/server/armeria.md | 8 ++++---- generated-doc/out/server/aws.md | 6 +++--- generated-doc/out/server/finatra.md | 4 ++-- generated-doc/out/server/http4s.md | 2 +- generated-doc/out/server/netty.md | 8 ++++---- generated-doc/out/server/observability.md | 8 ++++---- generated-doc/out/server/play.md | 2 +- generated-doc/out/server/vertx.md | 8 ++++---- generated-doc/out/server/zio-http4s.md | 6 +++--- generated-doc/out/server/ziohttp.md | 4 ++-- generated-doc/out/testing.md | 10 ++++++---- 23 files changed, 66 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 0fe666a37c..5dfec0beab 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ tapir documentation is available at [tapir.softwaremill.com](http://tapir.softwa Add the following dependency: ```sbt -"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.6" ``` Then, import: diff --git a/generated-doc/out/client/http4s.md b/generated-doc/out/client/http4s.md index 3b85c396db..1cf49e8967 100644 --- a/generated-doc/out/client/http4s.md +++ b/generated-doc/out/client/http4s.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-http4s-client" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-client" % "1.2.6" ``` To interpret an endpoint definition as an `org.http4s.Request[F]`, import: diff --git a/generated-doc/out/client/play.md b/generated-doc/out/client/play.md index fdafd852e2..672837a4c1 100644 --- a/generated-doc/out/client/play.md +++ b/generated-doc/out/client/play.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-play-client" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-play-client" % "1.2.6" ``` To make requests using an endpoint definition using the [play client](https://github.com/playframework/play-ws), import: diff --git a/generated-doc/out/client/sttp.md b/generated-doc/out/client/sttp.md index 904dc08c13..980364afea 100644 --- a/generated-doc/out/client/sttp.md +++ b/generated-doc/out/client/sttp.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % "1.2.6" ``` To make requests using an endpoint definition using the [sttp client](https://github.com/softwaremill/sttp), import: @@ -102,7 +102,7 @@ In this case add the following dependencies (note the [`%%%`](https://www.scala- instead of the usual `%%`): ```scala -"com.softwaremill.sttp.tapir" %%% "tapir-sttp-client" % "1.2.5" +"com.softwaremill.sttp.tapir" %%% "tapir-sttp-client" % "1.2.6" "io.github.cquiroz" %%% "scala-java-time" % "2.2.0" // implementations of java.time classes for Scala.JS ``` diff --git a/generated-doc/out/docs/asyncapi.md b/generated-doc/out/docs/asyncapi.md index 6acb41f342..78b8200602 100644 --- a/generated-doc/out/docs/asyncapi.md +++ b/generated-doc/out/docs/asyncapi.md @@ -3,7 +3,7 @@ To use, add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-asyncapi-docs" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-asyncapi-docs" % "1.2.6" "com.softwaremill.sttp.apispec" %% "asyncapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` diff --git a/generated-doc/out/docs/openapi.md b/generated-doc/out/docs/openapi.md index 167f472564..92f792b078 100644 --- a/generated-doc/out/docs/openapi.md +++ b/generated-doc/out/docs/openapi.md @@ -13,7 +13,7 @@ these steps can be done separately, giving you complete control over the process To generate OpenAPI documentation and expose it using the Swagger UI in a single step, first add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % "1.2.6" ``` Then, you can interpret a list of endpoints using `SwaggerInterpreter`. The result will be a list of file-serving @@ -55,7 +55,7 @@ for details. Similarly as above, you'll need the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-redoc-bundle" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-redoc-bundle" % "1.2.6" ``` And the server endpoints can be generated using the `sttp.tapir.redoc.bundle.RedocInterpreter` class. @@ -65,7 +65,7 @@ And the server endpoints can be generated using the `sttp.tapir.redoc.bundle.Red To generate the docs in the OpenAPI yaml format, add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.6" "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` @@ -133,7 +133,7 @@ For example, generating the OpenAPI 3.1.0 YAML string can be achieved by perform Firstly add dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.6" "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` @@ -164,12 +164,12 @@ The modules `tapir-swagger-ui` and `tapir-redoc` contain server endpoint definit yaml format, will expose it using the given context path. To use, add as a dependency either `tapir-swagger-ui`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui" % "1.2.6" ``` or `tapir-redoc`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-redoc" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-redoc" % "1.2.6" ``` Then, you'll need to pass the server endpoints to your server interpreter. For example, using akka-http: diff --git a/generated-doc/out/endpoint/integrations.md b/generated-doc/out/endpoint/integrations.md index e5169f85e6..f47548632c 100644 --- a/generated-doc/out/endpoint/integrations.md +++ b/generated-doc/out/endpoint/integrations.md @@ -14,7 +14,7 @@ The `tapir-cats` module contains additional instances for some [cats](https://ty datatypes as well as additional syntax: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-cats" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-cats" % "1.2.6" ``` - `import sttp.tapir.integ.cats.codec._` - brings schema, validator and codec instances @@ -26,7 +26,7 @@ If you use [refined](https://github.com/fthomas/refined), the `tapir-refined` mo validators for `T Refined P` as long as a codec for `T` already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-refined" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-refined" % "1.2.6" ``` You'll need to extend the `sttp.tapir.codec.refined.TapirCodecRefined` @@ -47,7 +47,7 @@ The `tapir-enumeratum` module provides schemas, validators and codecs for [Enume enumerations. To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % "1.2.6" ``` Then, `import sttp.tapir.codec.enumeratum._`, or extends the `sttp.tapir.codec.enumeratum.TapirCodecEnumeratum` trait. @@ -60,7 +60,7 @@ If you use [scala-newtype](https://github.com/estatico/scala-newtype), the `tapi schemas for types with a `@newtype` and `@newsubtype` annotations as long as a codec and schema for its underlying value already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-newtype" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-newtype" % "1.2.6" ``` Then, `import sttp.tapir.codec.newtype._`, or extend the `sttp.tapir.codec.newtype.TapirCodecNewType` trait to bring the implicit values into scope. @@ -71,7 +71,7 @@ If you use [monix newtypes](https://github.com/monix/newtypes), the `tapir-monix schemas for types which extend `NewtypeWrapped` and `NewsubtypeWrapped` annotations as long as a codec and schema for its underlying value already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-monix-newtype" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-monix-newtype" % "1.2.6" ``` Then, `import sttp.tapir.codec.monix.newtype._`, or extend the `sttp.tapir.codec.monix.newtype.TapirCodecMonixNewType` trait to bring the implicit values into scope. @@ -82,7 +82,7 @@ If you use [ZIO Prelude Newtypes](https://zio.github.io/zio-prelude/docs/newtype schemas for types defined using `Newtype` and `Subtype` as long as a codec and a schema for the underlying type already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio-prelude" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-zio-prelude" % "1.2.6" ``` Then, mix in `sttp.tapir.codec.zio.prelude.newtype.TapirNewtypeSupport` into your newtype to bring the implicit values into scope: @@ -121,7 +121,7 @@ For details refer to [derevo documentation](https://github.com/tofu-tf/derevo#in To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-derevo" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-derevo" % "1.2.6" ``` Then you can derive schema for your ADT along with other typeclasses besides ADT declaration itself: diff --git a/generated-doc/out/endpoint/json.md b/generated-doc/out/endpoint/json.md index e4479efcba..abc9bb12eb 100644 --- a/generated-doc/out/endpoint/json.md +++ b/generated-doc/out/endpoint/json.md @@ -45,7 +45,7 @@ stringJsonBody.schema(implicitly[Schema[MyBody]].as[String]) To use [Circe](https://github.com/circe/circe), add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.2.6" ``` Next, import the package (or extend the `TapirJsonCirce` trait, see [MyTapir](../mytapir.md)): @@ -118,7 +118,7 @@ Now the above JSON object will render as To use [µPickle](http://www.lihaoyi.com/upickle/) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-upickle" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-json-upickle" % "1.2.6" ``` Next, import the package (or extend the `TapirJsonuPickle` trait, see [MyTapir](../mytapir.md) and add `TapirJsonuPickle` not `TapirCirceJson`): @@ -153,7 +153,7 @@ For more examples, including making a custom encoder/decoder, see [TapirJsonuPic To use [Play JSON](https://github.com/playframework/play-json) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-play" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-json-play" % "1.2.6" ``` Next, import the package (or extend the `TapirJsonPlay` trait, see [MyTapir](../mytapir.md) and add `TapirJsonPlay` not `TapirCirceJson`): @@ -169,7 +169,7 @@ Play JSON requires `Reads` and `Writes` implicit values in scope for each type y To use [Spray JSON](https://github.com/spray/spray-json) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-spray" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-json-spray" % "1.2.6" ``` Next, import the package (or extend the `TapirJsonSpray` trait, see [MyTapir](../mytapir.md) and add `TapirJsonSpray` not `TapirCirceJson`): @@ -185,7 +185,7 @@ Spray JSON requires a `JsonFormat` implicit value in scope for each type you wan To use [Tethys JSON](https://github.com/tethys-json/tethys) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-tethys" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-json-tethys" % "1.2.6" ``` Next, import the package (or extend the `TapirJsonTethys` trait, see [MyTapir](../mytapir.md) and add `TapirJsonTethys` not `TapirCirceJson`): @@ -201,7 +201,7 @@ Tethys JSON requires `JsonReader` and `JsonWriter` implicit values in scope for To use [Jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "1.2.6" ``` Next, import the package (or extend the `TapirJsonJsoniter` trait, see [MyTapir](../mytapir.md) and add `TapirJsonJsoniter` not `TapirCirceJson`): @@ -217,7 +217,7 @@ Jsoniter Scala requires `JsonValueCodec` implicit value in scope for each type y To use [json4s](https://github.com/json4s/json4s) add the following dependencies to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-json4s" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-json-json4s" % "1.2.6" ``` And one of the implementations: @@ -248,7 +248,7 @@ implicit val formats: Formats = org.json4s.jackson.Serialization.formats(NoTypeH To use [zio-json](https://github.com/zio/zio-json), add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "1.2.6" ``` Next, import the package (or extend the `TapirJsonZio` trait, see [MyTapir](../mytapir.md) and add `TapirJsonZio` instead of `TapirCirceJson`): diff --git a/generated-doc/out/examples.md b/generated-doc/out/examples.md index 93967c360d..31112afd98 100644 --- a/generated-doc/out/examples.md +++ b/generated-doc/out/examples.md @@ -4,7 +4,7 @@ The [examples](https://github.com/softwaremill/tapir/tree/master/examples/src/ma ## Generate a tapir project -You can generate a simple project using tapir using [adopt-tapir](https://adopt-tapir.softwaremill.com). +You can generate a simple tapir-based project using chosen features, build tool and effect system using [adopt-tapir](https://adopt-tapir.softwaremill.com). ## Third-party examples diff --git a/generated-doc/out/generator/sbt-openapi-codegen.md b/generated-doc/out/generator/sbt-openapi-codegen.md index e62213a54a..dcdaf053bc 100644 --- a/generated-doc/out/generator/sbt-openapi-codegen.md +++ b/generated-doc/out/generator/sbt-openapi-codegen.md @@ -11,7 +11,7 @@ Add the sbt plugin to the `project/plugins.sbt`: ```scala -addSbtPlugin("com.softwaremill.sttp.tapir" % "sbt-openapi-codegen" % "1.2.5") +addSbtPlugin("com.softwaremill.sttp.tapir" % "sbt-openapi-codegen" % "1.2.6") ``` Enable the plugin for your project in the `build.sbt`: diff --git a/generated-doc/out/quickstart.md b/generated-doc/out/quickstart.md index 12df2a8872..eb026dced9 100644 --- a/generated-doc/out/quickstart.md +++ b/generated-doc/out/quickstart.md @@ -3,7 +3,7 @@ To use tapir, add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.6" ``` This will import only the core classes needed to create endpoint descriptions. To generate a server or a client, you diff --git a/generated-doc/out/server/akkahttp.md b/generated-doc/out/server/akkahttp.md index e94cdefc09..4112000ecd 100644 --- a/generated-doc/out/server/akkahttp.md +++ b/generated-doc/out/server/akkahttp.md @@ -4,14 +4,14 @@ To expose an endpoint as an [akka-http](https://doc.akka.io/docs/akka-http/curre dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.6" ``` This will transitively pull some Akka modules in version 2.6. If you want to force your own Akka version (for example 2.5), use sbt exclusion. Mind the Scala version in artifact name: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.5" exclude("com.typesafe.akka", "akka-stream_2.12") +"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.6" exclude("com.typesafe.akka", "akka-stream_2.12") ``` Now import the object: diff --git a/generated-doc/out/server/armeria.md b/generated-doc/out/server/armeria.md index 2b9dfd60f6..8e7b067f5c 100644 --- a/generated-doc/out/server/armeria.md +++ b/generated-doc/out/server/armeria.md @@ -8,7 +8,7 @@ Armeria interpreter can be used with different effect systems (cats-effect, ZIO) Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server" % "1.2.6" ``` and import the object: @@ -75,7 +75,7 @@ Note that Armeria automatically injects an `ExecutionContext` on top of Armeria' Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-cats" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-cats" % "1.2.6" ``` to use this interpreter with Cats Effect typeclasses. @@ -155,9 +155,9 @@ Add the following dependency ```scala // for zio 2: -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio" % "1.2.6" // for zio 1: -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio1" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio1" % "1.2.6" ``` to use this interpreter with ZIO. diff --git a/generated-doc/out/server/aws.md b/generated-doc/out/server/aws.md index 1a2a4b7a64..9df5cff57c 100644 --- a/generated-doc/out/server/aws.md +++ b/generated-doc/out/server/aws.md @@ -13,7 +13,7 @@ To implement the Lambda function, a server interpreter is available, which takes Currently, only an interpreter integrating with cats-effect is available (`AwsCatsEffectServerInterpreter`). To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-aws-lambda" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-aws-lambda" % "1.2.6" ``` To configure API Gateway and the Lambda function, you can use: @@ -24,8 +24,8 @@ To configure API Gateway and the Lambda function, you can use: Add one of the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-aws-sam" % "1.2.5" -"com.softwaremill.sttp.tapir" %% "tapir-aws-terraform" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-aws-sam" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-aws-terraform" % "1.2.6" ``` ## Examples diff --git a/generated-doc/out/server/finatra.md b/generated-doc/out/server/finatra.md index bc2c8c3879..60aabfe5a7 100644 --- a/generated-doc/out/server/finatra.md +++ b/generated-doc/out/server/finatra.md @@ -4,7 +4,7 @@ To expose an endpoint as an [finatra](https://twitter.github.io/finatra/) server dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-finatra-server" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-finatra-server" % "1.2.6" ``` and import the object: @@ -17,7 +17,7 @@ This interpreter supports the twitter `Future`. Or, if you would like to use cats-effect project, you can add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-finatra-server-cats" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-finatra-server-cats" % "1.2.6" ``` and import the object: diff --git a/generated-doc/out/server/http4s.md b/generated-doc/out/server/http4s.md index a2e19689a5..b567b832ff 100644 --- a/generated-doc/out/server/http4s.md +++ b/generated-doc/out/server/http4s.md @@ -4,7 +4,7 @@ To expose an endpoint as an [http4s](https://http4s.org) server, first add the f dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % "1.2.6" ``` and import the object: diff --git a/generated-doc/out/server/netty.md b/generated-doc/out/server/netty.md index 923dca5511..f214b27db2 100644 --- a/generated-doc/out/server/netty.md +++ b/generated-doc/out/server/netty.md @@ -4,16 +4,16 @@ To expose an endpoint using a [Netty](https://netty.io)-based server, first add ```scala // if you are using Future or just exploring -"com.softwaremill.sttp.tapir" %% "tapir-netty-server" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server" % "1.2.6" // if you are using cats-effect: -"com.softwaremill.sttp.tapir" %% "tapir-netty-server-cats" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-cats" % "1.2.6" // if you are using zio: -"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio" % "1.2.6" // if you are using zio1: -"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio1" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio1" % "1.2.6" ``` Then, use: diff --git a/generated-doc/out/server/observability.md b/generated-doc/out/server/observability.md index cf9ea58e35..716362c3cc 100644 --- a/generated-doc/out/server/observability.md +++ b/generated-doc/out/server/observability.md @@ -49,7 +49,7 @@ val labels = MetricLabels( Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-prometheus-metrics" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-prometheus-metrics" % "1.2.6" ``` `PrometheusMetrics` encapsulates `CollectorReqistry` and `Metric` instances. It provides several ready to use metrics as @@ -130,7 +130,7 @@ val prometheusMetrics = PrometheusMetrics[Future]("tapir", CollectorRegistry.def Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-opentelemetry-metrics" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-opentelemetry-metrics" % "1.2.6" ``` OpenTelemetry metrics are vendor-agnostic and can be exported using one @@ -157,7 +157,7 @@ val metricsInterceptor = metrics.metricsInterceptor() // add to your server opti Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-datadog-metrics" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-datadog-metrics" % "1.2.6" ``` Datadog metrics are sent as Datadog custom metrics through @@ -224,7 +224,7 @@ val datadogMetrics = DatadogMetrics.default[Future](statsdClient) Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio-metrics" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-zio-metrics" % "1.2.6" ``` Metrics have been integrated into ZIO core in ZIO2. diff --git a/generated-doc/out/server/play.md b/generated-doc/out/server/play.md index 4537183376..9b1dae7423 100644 --- a/generated-doc/out/server/play.md +++ b/generated-doc/out/server/play.md @@ -3,7 +3,7 @@ To expose endpoint as a [play-server](https://www.playframework.com/) first add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-play-server" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-play-server" % "1.2.6" ``` and (if you don't already depend on Play) diff --git a/generated-doc/out/server/vertx.md b/generated-doc/out/server/vertx.md index f362701548..89fcbd878c 100644 --- a/generated-doc/out/server/vertx.md +++ b/generated-doc/out/server/vertx.md @@ -8,7 +8,7 @@ Vert.x interpreter can be used with different effect systems (cats-effect, ZIO) Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server" % "1.2.6" ``` to use this interpreter with `Future`. @@ -63,7 +63,7 @@ It's also possible to define an endpoint together with the server logic in a sin Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-cats" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-cats" % "1.2.6" ``` to use this interpreter with Cats Effect typeclasses. @@ -146,9 +146,9 @@ Add the following dependency ```scala // for zio2: -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio" % "1.2.6" // for zio1: -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio1" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio1" % "1.2.6" ``` to use this interpreter with ZIO. diff --git a/generated-doc/out/server/zio-http4s.md b/generated-doc/out/server/zio-http4s.md index 0a86be2011..6d14a0ebdd 100644 --- a/generated-doc/out/server/zio-http4s.md +++ b/generated-doc/out/server/zio-http4s.md @@ -9,16 +9,16 @@ The `*-zio` modules depend on ZIO 2.x. For ZIO 1.x support, use modules with the You'll need the following dependency for the `ZServerEndpoint` type alias and helper classes: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.6" ``` or just add the zio-http4s integration which already depends on `tapir-zio`: ```scala // for zio 2: -"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio" % "1.2.6" // for zio 1: -"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio1" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio1" % "1.2.6" ``` Next, instead of the usual `import sttp.tapir._`, you should import (or extend the `ZTapir` trait, see [MyTapir](../mytapir.md)): diff --git a/generated-doc/out/server/ziohttp.md b/generated-doc/out/server/ziohttp.md index 3ceb92c6c1..f8d705ac55 100644 --- a/generated-doc/out/server/ziohttp.md +++ b/generated-doc/out/server/ziohttp.md @@ -9,13 +9,13 @@ The `*-zio` modules depend on ZIO 2.x. For ZIO 1.x support, use modules with the You'll need the following dependency for the `ZServerEndpoint` type alias and helper classes: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.6" ``` or just add the zio-http integration which already depends on `tapir-zio`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio-http-server" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-zio-http-server" % "1.2.6" ``` Next, instead of the usual `import sttp.tapir._`, you should import (or extend the `ZTapir` trait, see [MyTapir](../mytapir.md)): diff --git a/generated-doc/out/testing.md b/generated-doc/out/testing.md index 484825c8c9..ad5581b670 100644 --- a/generated-doc/out/testing.md +++ b/generated-doc/out/testing.md @@ -23,7 +23,7 @@ Tapir builds upon the `SttpBackendStub` to enable stubbing using `Endpoint`s or dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.6" ``` Let's assume you are using the [akka http](server/akkahttp.md) interpreter. Given the following server endpoint: @@ -80,6 +80,8 @@ class MySpec extends AsyncFlatSpec with Matchers { The `.backend` method creates the enriched `SttpBackendStub`, using the provided server endpoints and their behaviors. Any requests will be handled by a stub server interpreter, using the complete request handling logic. +Projects generated using [adopt-tapir](https://adopt-tapir.softwaremill.com) include a test which uses the above approach. + ### Custom interpreters Custom interpreters can be provided to the stub. For example, to test custom exception handling, we might have the @@ -140,7 +142,7 @@ requests matching an endpoint, you can use the tapir `SttpBackendStub` extension Similarly as when testing server interpreters, add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.6" ``` And the following imports: @@ -195,7 +197,7 @@ with [mock-server](https://www.mock-server.com/) Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-mock-server" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-mock-server" % "1.2.6" ``` Imports: @@ -266,7 +268,7 @@ result == out To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-testing" % "1.2.5" +"com.softwaremill.sttp.tapir" %% "tapir-testing" % "1.2.6" ``` ### Shadowed endpoints From ba21df20d92fb871c12a6717ae7ae07ebf72d8e5 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Tue, 24 Jan 2023 00:05:13 +0000 Subject: [PATCH 101/120] Update java-dogstatsd-client to 4.2.0 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 581d4432ef..d8eb840f42 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -52,6 +52,6 @@ object Versions { val quicklens = "1.9.0" val openTelemetry = "1.22.0" val mockServer = "5.14.0" - val dogstatsdClient = "4.1.0" + val dogstatsdClient = "4.2.0" val nettyAll = "4.1.86.Final" } From 24bbe9228dab2b69146d7ab7afbd0fd7d2d48ccc Mon Sep 17 00:00:00 2001 From: scala-steward Date: Tue, 24 Jan 2023 00:05:23 +0000 Subject: [PATCH 102/120] Update scalafmt-core to 3.7.1 --- .scalafmt.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 29bfa10437..10c71c7d21 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = 3.6.1 +version = 3.7.1 maxColumn = 140 runner.dialect = scala3 fileOverride { From 5629d39bd05c54bc957996ca4bf46c4bab1cbd67 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Tue, 24 Jan 2023 00:05:45 +0000 Subject: [PATCH 103/120] Reformat with scalafmt 3.7.1 Executed command: scalafmt --non-interactive --- build.sbt | 4 +- .../sttp/tapir/CodecExtensions2.scala | 2 +- .../internal/ValidatorEnumerationMacro.scala | 8 ++- .../sttp/tapir/macros/SchemaMacros.scala | 2 - .../sttp/tapir/model/package.scala | 1 + .../sttp/tapir/macros/SchemaMacros.scala | 6 +-- core/src/main/scala/sttp/tapir/Codec.scala | 6 +-- .../examples/HelloWorldZioHttpServer.scala | 11 ++-- .../examples/ZioExampleZioHttpServer.scala | 2 +- .../observability/ZioMetricsExample.scala | 2 +- .../examples/openapi/RedocZioHttpServer.scala | 2 +- .../streaming/StreamingZioHttpServer.scala | 2 +- .../protobuf/EndpointToProtobufMessage.scala | 1 - .../grpc/protobuf/model/ProtobufMessage.scala | 2 +- .../grpc/protobuf/ProtoRendererTest.scala | 2 +- .../enumeratum/TapirCodecEnumeratum.scala | 9 +++- .../enumeratum/TapirCodecEnumeratumTest.scala | 4 +- .../tapir/server/metrics/zio/ZioMetrics.scala | 17 +++---- .../server/metrics/zio/ZioMetricsTest.scala | 50 +++++++++++-------- .../finatra/FinatraServerInterpreter.scala | 49 +++++++++--------- .../server/netty/NettyResponseContent.scala | 5 +- .../netty/internal/NettyToResponseBody.scala | 6 ++- .../netty/zio/NettyZioServerOptions.scala | 1 - .../netty/zio/NettyZioServerOptions.scala | 1 - .../vertx/VertxTestServerInterpreter.scala | 3 +- .../aws/lambda/js/AwsJsRouteHandler.scala | 5 +- .../aws/sam/VerifySamTemplateTest.scala | 7 ++- 27 files changed, 118 insertions(+), 92 deletions(-) diff --git a/build.sbt b/build.sbt index bbf9959598..1ce5576d4d 100644 --- a/build.sbt +++ b/build.sbt @@ -528,10 +528,10 @@ lazy val enumeratum: ProjectMatrix = (projectMatrix in file("integrations/enumer "com.beachape" %%% "enumeratum" % Versions.enumeratum, scalaTest.value % Test ), - Test/scalacOptions ++= { + Test / scalacOptions ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((3, _)) => Seq("-Yretain-trees") - case _ => Seq() + case _ => Seq() } } ) diff --git a/core/src/main/scala-2.13-/sttp/tapir/CodecExtensions2.scala b/core/src/main/scala-2.13-/sttp/tapir/CodecExtensions2.scala index 40a8a17978..39167c926a 100644 --- a/core/src/main/scala-2.13-/sttp/tapir/CodecExtensions2.scala +++ b/core/src/main/scala-2.13-/sttp/tapir/CodecExtensions2.scala @@ -1,3 +1,3 @@ package sttp.tapir -trait CodecExtensions2 \ No newline at end of file +trait CodecExtensions2 diff --git a/core/src/main/scala-2/sttp/tapir/internal/ValidatorEnumerationMacro.scala b/core/src/main/scala-2/sttp/tapir/internal/ValidatorEnumerationMacro.scala index 1ccf544096..66fe0ea34b 100644 --- a/core/src/main/scala-2/sttp/tapir/internal/ValidatorEnumerationMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/internal/ValidatorEnumerationMacro.scala @@ -42,13 +42,11 @@ private[tapir] object ValidatorEnumerationMacro { val enumNameComponents = weakTypeT.toString.split("\\.").dropRight(1) val enumeration = enumNameComponents.toList match { case head :: tail => tail.foldLeft[Tree](Ident(TermName(head))) { case (tree, nextName) => Select(tree, TermName(nextName)) } - case Nil => c.abort(c.enclosingPosition, s"Invalid enum name: ${weakTypeT.toString}") + case Nil => c.abort(c.enclosingPosition, s"Invalid enum name: ${weakTypeT.toString}") } - q"_root_.sttp.tapir.Validator.enumeration($enumeration.values.toList, v => Option(v), Some(sttp.tapir.Schema.SName(${ - enumNameComponents - .mkString(".") - })))" + q"_root_.sttp.tapir.Validator.enumeration($enumeration.values.toList, v => Option(v), Some(sttp.tapir.Schema.SName(${enumNameComponents + .mkString(".")})))" } } } diff --git a/core/src/main/scala-2/sttp/tapir/macros/SchemaMacros.scala b/core/src/main/scala-2/sttp/tapir/macros/SchemaMacros.scala index cb2aa7d95a..a5c097e09a 100644 --- a/core/src/main/scala-2/sttp/tapir/macros/SchemaMacros.scala +++ b/core/src/main/scala-2/sttp/tapir/macros/SchemaMacros.scala @@ -83,5 +83,3 @@ trait SchemaCompanionMacros extends SchemaMagnoliaDerivation { */ implicit def derivedEnumerationValue[T <: scala.Enumeration#Value]: Schema[T] = macro SchemaEnumerationMacro.derivedEnumerationValue[T] } - - diff --git a/core/src/main/scala-3-2.13+/sttp/tapir/model/package.scala b/core/src/main/scala-3-2.13+/sttp/tapir/model/package.scala index f6f42b5768..24d5836339 100644 --- a/core/src/main/scala-3-2.13+/sttp/tapir/model/package.scala +++ b/core/src/main/scala-3-2.13+/sttp/tapir/model/package.scala @@ -1,6 +1,7 @@ package sttp.tapir package object model { + /** Used to lookup codecs which split/combine values using a comma. */ type CommaSeparated[T] = Delimited[",", T] } diff --git a/core/src/main/scala-3/sttp/tapir/macros/SchemaMacros.scala b/core/src/main/scala-3/sttp/tapir/macros/SchemaMacros.scala index 0a079d936a..7790881e10 100644 --- a/core/src/main/scala-3/sttp/tapir/macros/SchemaMacros.scala +++ b/core/src/main/scala-3/sttp/tapir/macros/SchemaMacros.scala @@ -254,11 +254,11 @@ private[tapir] object SchemaCompanionMacros { TypeIdent(subclass).tpe.asType match { case '[f] => { Expr.summon[Schema[f]] match { - case Some (subSchema) => '{${Expr (subclass.name)} -> Schema.wrapWithSingleFieldProduct (${subSchema}) ($conf)} + case Some(subSchema) => '{ ${ Expr(subclass.name) } -> Schema.wrapWithSingleFieldProduct(${ subSchema })($conf) } case None => { val typeName = TypeRepr.of[f].typeSymbol.name - report.errorAndAbort (s"Cannot summon schema for `${typeName}`. Make sure schema derivation is properly configured.") - } + report.errorAndAbort(s"Cannot summon schema for `${typeName}`. Make sure schema derivation is properly configured.") + } } } } diff --git a/core/src/main/scala/sttp/tapir/Codec.scala b/core/src/main/scala/sttp/tapir/Codec.scala index b3b6ac2f83..d276741366 100644 --- a/core/src/main/scala/sttp/tapir/Codec.scala +++ b/core/src/main/scala/sttp/tapir/Codec.scala @@ -569,9 +569,9 @@ object Codec extends CodecExtensions with CodecExtensions2 with FormCodecMacros } }(_.toString) - implicit val contentRange: Codec[String, ContentRange, CodecFormat.TextPlain] = Codec.string.mapDecode { v => - DecodeResult.fromEitherString(v, ContentRange.parse(v)) - }(_.toString) + implicit val contentRange: Codec[String, ContentRange, CodecFormat.TextPlain] = Codec.string.mapDecode { v => + DecodeResult.fromEitherString(v, ContentRange.parse(v)) + }(_.toString) implicit val cacheDirective: Codec[String, List[CacheDirective], CodecFormat.TextPlain] = Codec.string.mapDecode { v => @tailrec diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala index 0d90106304..e164aa2eae 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala @@ -38,8 +38,11 @@ object HelloWorldZioHttpServer extends ZIOAppDefault { // starting the server override def run = - Server.serve(app).provide( - ServerConfig.live(ServerConfig.default.port(8090)), - Server.live, - ).exitCode + Server + .serve(app) + .provide( + ServerConfig.live(ServerConfig.default.port(8090)), + Server.live + ) + .exitCode } diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala index c60176d6f9..6c91dfd8b8 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala @@ -46,7 +46,7 @@ object ZioExampleZioHttpServer extends ZIOAppDefault { .serve(routes) .provide( ServerConfig.live(ServerConfig.default.port(8080)), - Server.live, + Server.live ) .exitCode } diff --git a/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala b/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala index 3ed4dbf38b..662d7269b9 100644 --- a/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala @@ -40,7 +40,7 @@ object ZioMetricsExample extends ZIOAppDefault { } yield serverPort) .provide( ServerConfig.live(ServerConfig.default.port(port)), - Server.live, + Server.live ) .exitCode } diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala index 71844e3588..0ed4087851 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala @@ -32,7 +32,7 @@ object RedocZioHttpServer extends ZIOAppDefault { .serve(petRoutes ++ redocRoutes) .provide( ServerConfig.live(ServerConfig.default.port(8080)), - Server.live, + Server.live ) .fork .flatMap { fiber => diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala index f7b595f2a0..ef00110c71 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala @@ -45,7 +45,7 @@ object StreamingZioHttpServer extends ZIOAppDefault { .serve(routes) .provide( ServerConfig.live(ServerConfig.default.port(8080)), - Server.live, + Server.live ) .exitCode } diff --git a/grpc/protobuf/src/main/scala/sttp/tapir/grpc/protobuf/EndpointToProtobufMessage.scala b/grpc/protobuf/src/main/scala/sttp/tapir/grpc/protobuf/EndpointToProtobufMessage.scala index 4f3f1b295f..d2527f4e45 100644 --- a/grpc/protobuf/src/main/scala/sttp/tapir/grpc/protobuf/EndpointToProtobufMessage.scala +++ b/grpc/protobuf/src/main/scala/sttp/tapir/grpc/protobuf/EndpointToProtobufMessage.scala @@ -77,7 +77,6 @@ class EndpointToProtobufMessage { } List(ProtobufProductMessage(toMessageName(name), protoFields)) case SCoproduct(subtypes, discriminator) => - List( ProtobufCoproductMessage( toMessageName(name), diff --git a/grpc/protobuf/src/main/scala/sttp/tapir/grpc/protobuf/model/ProtobufMessage.scala b/grpc/protobuf/src/main/scala/sttp/tapir/grpc/protobuf/model/ProtobufMessage.scala index a05b96b101..515040a8bc 100644 --- a/grpc/protobuf/src/main/scala/sttp/tapir/grpc/protobuf/model/ProtobufMessage.scala +++ b/grpc/protobuf/src/main/scala/sttp/tapir/grpc/protobuf/model/ProtobufMessage.scala @@ -7,4 +7,4 @@ sealed trait ProtobufMessage { } case class ProtobufProductMessage(name: MessageName, fields: Iterable[ProtobufMessageField]) extends ProtobufMessage -case class ProtobufCoproductMessage(name: MessageName, alternatives: Iterable[ProtobufMessageField]) extends ProtobufMessage \ No newline at end of file +case class ProtobufCoproductMessage(name: MessageName, alternatives: Iterable[ProtobufMessageField]) extends ProtobufMessage diff --git a/grpc/protobuf/src/test/scala/sttp/tapir/grpc/protobuf/ProtoRendererTest.scala b/grpc/protobuf/src/test/scala/sttp/tapir/grpc/protobuf/ProtoRendererTest.scala index d9abfcb389..2cb58bed2b 100644 --- a/grpc/protobuf/src/test/scala/sttp/tapir/grpc/protobuf/ProtoRendererTest.scala +++ b/grpc/protobuf/src/test/scala/sttp/tapir/grpc/protobuf/ProtoRendererTest.scala @@ -211,7 +211,7 @@ class ProtoRendererTest extends AnyFlatSpec with ProtobufMatchers { "Format", List( ProtobufMessageField(ProtobufMessageRef(SName("Epub")), "epub", None), - ProtobufMessageField(ProtobufMessageRef(SName("Paper")), "paper", None), + ProtobufMessageField(ProtobufMessageRef(SName("Paper")), "paper", None) ) ), ProtobufProductMessage( diff --git a/integrations/enumeratum/src/main/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratum.scala b/integrations/enumeratum/src/main/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratum.scala index 4fdfd5ad50..1b5aec0b6d 100644 --- a/integrations/enumeratum/src/main/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratum.scala +++ b/integrations/enumeratum/src/main/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratum.scala @@ -14,7 +14,9 @@ trait TapirCodecEnumeratum { implicit def schemaForEnumEntry[E <: EnumEntry](implicit annotations: SchemaAnnotations[E], `enum`: Enum[E]): Schema[E] = annotations.enrich(Schema[E](SchemaType.SString()).validate(validatorEnumEntry)) - def plainCodecEnumEntryUsing[E <: EnumEntry](f: String => Option[E])(implicit `enum`: Enum[E]): Codec[String, E, CodecFormat.TextPlain] = { + def plainCodecEnumEntryUsing[E <: EnumEntry]( + f: String => Option[E] + )(implicit `enum`: Enum[E]): Codec[String, E, CodecFormat.TextPlain] = { val validator = validatorEnumEntry Codec.string .mapDecode { s => @@ -47,7 +49,10 @@ trait TapirCodecEnumeratum { implicit def schemaForShortEnumEntry[E <: ShortEnumEntry](implicit annotations: SchemaAnnotations[E], `enum`: ShortEnum[E]): Schema[E] = annotations.enrich(Schema[E](SchemaType.SInteger()).validate(validatorValueEnumEntry[Short, E])) - implicit def schemaForStringEnumEntry[E <: StringEnumEntry](implicit annotations: SchemaAnnotations[E], `enum`: StringEnum[E]): Schema[E] = + implicit def schemaForStringEnumEntry[E <: StringEnumEntry](implicit + annotations: SchemaAnnotations[E], + `enum`: StringEnum[E] + ): Schema[E] = annotations.enrich(Schema[E](SchemaType.SString()).validate(validatorValueEnumEntry[String, E])) implicit def schemaForByteEnumEntry[E <: ByteEnumEntry](implicit annotations: SchemaAnnotations[E], `enum`: ByteEnum[E]): Schema[E] = diff --git a/integrations/enumeratum/src/test/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratumTest.scala b/integrations/enumeratum/src/test/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratumTest.scala index 7bee203cf4..c136bb0313 100644 --- a/integrations/enumeratum/src/test/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratumTest.scala +++ b/integrations/enumeratum/src/test/scala/sttp/tapir/codec/enumeratum/TapirCodecEnumeratumTest.scala @@ -95,7 +95,9 @@ class TapirCodecEnumeratumTest extends AnyFlatSpec with Matchers { } } - private def testValueEnumPlainCodec[T, EE <: ValueEnumEntry[T], E <: ValueEnum[T, EE]](codec: PlainCodec[EE])(implicit `enum`: E): Unit = { + private def testValueEnumPlainCodec[T, EE <: ValueEnumEntry[T], E <: ValueEnum[T, EE]]( + codec: PlainCodec[EE] + )(implicit `enum`: E): Unit = { `enum`.values.foreach { v => codec.encode(v) shouldBe v.value.toString codec.decode(v.value.toString) shouldBe DecodeResult.Value(v) diff --git a/metrics/zio-metrics/src/main/scala/sttp/tapir/server/metrics/zio/ZioMetrics.scala b/metrics/zio-metrics/src/main/scala/sttp/tapir/server/metrics/zio/ZioMetrics.scala index 7120efa554..9515ed50d1 100644 --- a/metrics/zio-metrics/src/main/scala/sttp/tapir/server/metrics/zio/ZioMetrics.scala +++ b/metrics/zio-metrics/src/main/scala/sttp/tapir/server/metrics/zio/ZioMetrics.scala @@ -45,14 +45,14 @@ object ZioMetrics { Boundaries.fromChunk(Chunk(.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10, 15, 30, 45, 60)) /** Using the default namespace and labels, registers the following metrics: - * - * - `tapir_request_active{path, method}` (gauge) - * - `tapir_request_total{path, method, status}` (counter) - * - `tapir_request_duration_seconds{path, method, status, phase}` (histogram) - * - * Status is by default the status code class (1xx, 2xx, etc.), and phase can be either `headers` or `body` - request duration is - * measured separately up to the point where the headers are determined, and then once again when the whole response body is complete. - */ + * + * - `tapir_request_active{path, method}` (gauge) + * - `tapir_request_total{path, method, status}` (counter) + * - `tapir_request_duration_seconds{path, method, status, phase}` (histogram) + * + * Status is by default the status code class (1xx, 2xx, etc.), and phase can be either `headers` or `body` - request duration is + * measured separately up to the point where the headers are determined, and then once again when the whole response body is complete. + */ def default[F[_]](namespace: String = DefaultNamespace, labels: MetricLabels = MetricLabels.Default): ZioMetrics[F] = ZioMetrics( namespace, List( @@ -75,7 +75,6 @@ object ZioMetrics { def getRequestDurationHistogram(namespace: String): zio.metrics.Metric.Histogram[Double] = zio.metrics.Metric.histogram(s"${namespace}_request_duration_seconds", DurationBoundaries) - /** ZIO Unsafe Run Wrapper */ private def unsafeRun[T](task: Task[T]): T = Unsafe.unsafe { implicit u => runtime.unsafe.run(task.orDie).getOrThrowFiberFailure() diff --git a/metrics/zio-metrics/src/test/scala/sttp/tapir/server/metrics/zio/ZioMetricsTest.scala b/metrics/zio-metrics/src/test/scala/sttp/tapir/server/metrics/zio/ZioMetricsTest.scala index 9563eabca2..00cf1b4896 100644 --- a/metrics/zio-metrics/src/test/scala/sttp/tapir/server/metrics/zio/ZioMetricsTest.scala +++ b/metrics/zio-metrics/src/test/scala/sttp/tapir/server/metrics/zio/ZioMetricsTest.scala @@ -32,14 +32,18 @@ object ZioMetricsTest extends ZIOSpecDefault { ) // when - val active: Counter[Long] = ZioMetrics.getActiveRequestCounter("tapir").tagged( - Set(MetricLabel("path", "/person"), MetricLabel("method", "GET")) - ) + val active: Counter[Long] = ZioMetrics + .getActiveRequestCounter("tapir") + .tagged( + Set(MetricLabel("path", "/person"), MetricLabel("method", "GET")) + ) for { - _ <- ZIO.succeed({ - interpreter.apply(PersonsApi.request("Jacob")) - }).fork + _ <- ZIO + .succeed({ + interpreter.apply(PersonsApi.request("Jacob")) + }) + .fork _ <- ZIO.succeed(Thread.sleep(100)) state <- active.value _ <- ZIO.succeed(Thread.sleep(150)) @@ -63,13 +67,17 @@ object ZioMetricsTest extends ZIOSpecDefault { ) // when - val counter: Counter[Long] = ZioMetrics.getRequestsTotalCounter("tapir").tagged( - Set(MetricLabel("path", "/person"), MetricLabel("method", "GET"),MetricLabel("status", "2xx")) - ) + val counter: Counter[Long] = ZioMetrics + .getRequestsTotalCounter("tapir") + .tagged( + Set(MetricLabel("path", "/person"), MetricLabel("method", "GET"), MetricLabel("status", "2xx")) + ) - val missedCounter: Counter[Long] = ZioMetrics.getRequestsTotalCounter("tapir").tagged( - Set(MetricLabel("path", "/person"), MetricLabel("method", "GET"), MetricLabel("status", "4xx")) - ) + val missedCounter: Counter[Long] = ZioMetrics + .getRequestsTotalCounter("tapir") + .tagged( + Set(MetricLabel("path", "/person"), MetricLabel("method", "GET"), MetricLabel("status", "4xx")) + ) for { _ <- ZIO.succeed({ interpreter.apply(PersonsApi.request("Jacob")) @@ -80,10 +88,10 @@ object ZioMetricsTest extends ZIOSpecDefault { }) state <- counter.value stateMissed <- missedCounter.value - } yield assertTrue(state == MetricState.Counter(3D)) && - assertTrue(stateMissed == MetricState.Counter(2D)) + } yield assertTrue(state == MetricState.Counter(3d)) && + assertTrue(stateMissed == MetricState.Counter(2d)) }, - test ("can collect requests duration") { + test("can collect requests duration") { val serverEp = PersonsApi { name => PersonsApi.defaultLogic(name) }.serverEp @@ -98,9 +106,11 @@ object ZioMetricsTest extends ZIOSpecDefault { ) // when - val histogram: Metric[MetricKeyType.Histogram, Double, MetricState.Histogram] = ZioMetrics.getRequestDurationHistogram("tapir").tagged( - Set(MetricLabel("path", "/person"), MetricLabel("method", "GET"), MetricLabel("status", "2xx"), MetricLabel("phase", "headers")) - ) + val histogram: Metric[MetricKeyType.Histogram, Double, MetricState.Histogram] = ZioMetrics + .getRequestDurationHistogram("tapir") + .tagged( + Set(MetricLabel("path", "/person"), MetricLabel("method", "GET"), MetricLabel("status", "2xx"), MetricLabel("phase", "headers")) + ) for { _ <- ZIO.succeed({ @@ -109,8 +119,8 @@ object ZioMetricsTest extends ZIOSpecDefault { state <- histogram.value } yield assertTrue(state.buckets.exists(_._2 > 0L)) && assertTrue(state.count == 1L) && - assertTrue(state.min > 0D) && - assertTrue(state.max > 0D) + assertTrue(state.min > 0d) && + assertTrue(state.max > 0d) } ) } diff --git a/server/finatra-server/src/main/scala/sttp/tapir/server/finatra/FinatraServerInterpreter.scala b/server/finatra-server/src/main/scala/sttp/tapir/server/finatra/FinatraServerInterpreter.scala index 7847ca68ba..c8254279c3 100644 --- a/server/finatra-server/src/main/scala/sttp/tapir/server/finatra/FinatraServerInterpreter.scala +++ b/server/finatra-server/src/main/scala/sttp/tapir/server/finatra/FinatraServerInterpreter.scala @@ -26,34 +26,35 @@ trait FinatraServerInterpreter extends Logging { finatraServerOptions.deleteFile )(FutureMonadError, new FinatraBodyListener[Future]()) - val handler = { request: Request => - val serverRequest = new FinatraServerRequest(request) + val handler = { + request: Request => + val serverRequest = new FinatraServerRequest(request) - serverInterpreter(serverRequest).map { - case RequestResult.Failure(_) => Response(Status.NotFound) - case RequestResult.Response(response) => - val status = Status(response.code.code) - val responseWithContent = response.body match { - case Some(fContent) => - val response = fContent match { - case FinatraContentBuf(buf) => - val r = Response(Version.Http11, status) - r.content = buf - r - case FinatraContentReader(reader) => Response(Version.Http11, status, reader) - } - response - case None => - Response(Version.Http11, status) - } + serverInterpreter(serverRequest).map { + case RequestResult.Failure(_) => Response(Status.NotFound) + case RequestResult.Response(response) => + val status = Status(response.code.code) + val responseWithContent = response.body match { + case Some(fContent) => + val response = fContent match { + case FinatraContentBuf(buf) => + val r = Response(Version.Http11, status) + r.content = buf + r + case FinatraContentReader(reader) => Response(Version.Http11, status, reader) + } + response + case None => + Response(Version.Http11, status) + } - response.headers.foreach(header => responseWithContent.headerMap.add(header.name, header.value)) + response.headers.foreach(header => responseWithContent.headerMap.add(header.name, header.value)) - // If there's a content-type header in headers, override the content-type. - response.contentType.foreach(ct => responseWithContent.contentType = ct) + // If there's a content-type header in headers, override the content-type. + response.contentType.foreach(ct => responseWithContent.contentType = ct) - responseWithContent - } + responseWithContent + } } FinatraRoute(handler, httpMethod(se.endpoint), path(se.securityInput.and(se.input))) diff --git a/server/netty-server/src/main/scala/sttp/tapir/server/netty/NettyResponseContent.scala b/server/netty-server/src/main/scala/sttp/tapir/server/netty/NettyResponseContent.scala index 17def37bb0..dc79aebae2 100644 --- a/server/netty-server/src/main/scala/sttp/tapir/server/netty/NettyResponseContent.scala +++ b/server/netty-server/src/main/scala/sttp/tapir/server/netty/NettyResponseContent.scala @@ -8,8 +8,9 @@ sealed trait NettyResponseContent { def channelPromise: ChannelPromise } -object NettyResponseContent{ +object NettyResponseContent { final case class ByteBufNettyResponseContent(channelPromise: ChannelPromise, byteBuf: ByteBuf) extends NettyResponseContent - final case class ChunkedStreamNettyResponseContent(channelPromise: ChannelPromise, chunkedStream: ChunkedStream) extends NettyResponseContent + final case class ChunkedStreamNettyResponseContent(channelPromise: ChannelPromise, chunkedStream: ChunkedStream) + extends NettyResponseContent final case class ChunkedFileNettyResponseContent(channelPromise: ChannelPromise, chunkedFile: ChunkedFile) extends NettyResponseContent } diff --git a/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/NettyToResponseBody.scala b/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/NettyToResponseBody.scala index 1066ffd756..5a1fad15b9 100644 --- a/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/NettyToResponseBody.scala +++ b/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/NettyToResponseBody.scala @@ -8,7 +8,11 @@ import sttp.model.HasHeaders import sttp.tapir.capabilities.NoStreams import sttp.tapir.server.interpreter.ToResponseBody import sttp.tapir.server.netty.NettyResponse -import sttp.tapir.server.netty.NettyResponseContent.{ByteBufNettyResponseContent, ChunkedFileNettyResponseContent, ChunkedStreamNettyResponseContent} +import sttp.tapir.server.netty.NettyResponseContent.{ + ByteBufNettyResponseContent, + ChunkedFileNettyResponseContent, + ChunkedStreamNettyResponseContent +} import sttp.tapir.{CodecFormat, FileRange, RawBodyType, WebSocketBodyOutput} import java.io.{InputStream, RandomAccessFile} diff --git a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala index 29c3822851..da78e5fe37 100644 --- a/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala +++ b/server/netty-server/zio/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala @@ -57,5 +57,4 @@ object NettyZioServerOptions { private def debugLog[R](msg: String, exOpt: Option[Throwable]): RIO[R, Unit] = ZIO.succeed(NettyDefaults.debugLog(log, msg, exOpt)) - } diff --git a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala index c27185d13c..5d6b6c46b3 100644 --- a/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala +++ b/server/netty-server/zio1/src/main/scala/sttp/tapir/server/netty/zio/NettyZioServerOptions.scala @@ -57,5 +57,4 @@ object NettyZioServerOptions { private def debugLog[R](msg: String, exOpt: Option[Throwable]): RIO[R, Unit] = ZIO.succeed(NettyDefaults.debugLog(log, msg, exOpt)) - } diff --git a/server/vertx-server/src/test/scala/sttp/tapir/server/vertx/VertxTestServerInterpreter.scala b/server/vertx-server/src/test/scala/sttp/tapir/server/vertx/VertxTestServerInterpreter.scala index 4bf814c432..58c9b68e34 100644 --- a/server/vertx-server/src/test/scala/sttp/tapir/server/vertx/VertxTestServerInterpreter.scala +++ b/server/vertx-server/src/test/scala/sttp/tapir/server/vertx/VertxTestServerInterpreter.scala @@ -12,7 +12,8 @@ import sttp.tapir.tests.Port import scala.concurrent.Future -class VertxTestServerInterpreter(vertx: Vertx) extends TestServerInterpreter[Future, VertxStreams, VertxFutureServerOptions, Router => Route] { +class VertxTestServerInterpreter(vertx: Vertx) + extends TestServerInterpreter[Future, VertxStreams, VertxFutureServerOptions, Router => Route] { import VertxTestServerInterpreter._ override def route(es: List[ServerEndpoint[VertxStreams, Future]], interceptors: Interceptors): Router => Route = { router => diff --git a/serverless/aws/lambda/src/main/scalajs/sttp/tapir/serverless/aws/lambda/js/AwsJsRouteHandler.scala b/serverless/aws/lambda/src/main/scalajs/sttp/tapir/serverless/aws/lambda/js/AwsJsRouteHandler.scala index 6d81c5d2fc..760b163df6 100644 --- a/serverless/aws/lambda/src/main/scalajs/sttp/tapir/serverless/aws/lambda/js/AwsJsRouteHandler.scala +++ b/serverless/aws/lambda/src/main/scalajs/sttp/tapir/serverless/aws/lambda/js/AwsJsRouteHandler.scala @@ -11,8 +11,9 @@ import scala.scalajs.js.JSConverters._ object AwsJsRouteHandler { - private def toJsRoute[F[_]](route: Route[F])(implicit monadError: MonadError[F]): JsRoute[F] = { awsJsRequest: AwsJsRequest => - monadError.map(route.apply(AwsJsRequest.toAwsRequest(awsJsRequest)))(AwsJsResponse.fromAwsResponse) + private def toJsRoute[F[_]](route: Route[F])(implicit monadError: MonadError[F]): JsRoute[F] = { + awsJsRequest: AwsJsRequest => + monadError.map(route.apply(AwsJsRequest.toAwsRequest(awsJsRequest)))(AwsJsResponse.fromAwsResponse) } def futureHandler(event: AwsJsRequest, route: Route[Future])(implicit ec: ExecutionContext): scala.scalajs.js.Promise[AwsJsResponse] = { diff --git a/serverless/aws/sam/src/test/scala/sttp/tapir/serverless/aws/sam/VerifySamTemplateTest.scala b/serverless/aws/sam/src/test/scala/sttp/tapir/serverless/aws/sam/VerifySamTemplateTest.scala index bbaeed26b3..91535139bd 100644 --- a/serverless/aws/sam/src/test/scala/sttp/tapir/serverless/aws/sam/VerifySamTemplateTest.scala +++ b/serverless/aws/sam/src/test/scala/sttp/tapir/serverless/aws/sam/VerifySamTemplateTest.scala @@ -61,7 +61,12 @@ class VerifySamTemplateTest extends AnyFunSuite with Matchers { ) ) ), - source = CodeSource(runtime = "java11", codeUri = "/somewhere/pet-api.jar", "pet.api.Handler::handleRequest", environment = Map("myEnv" -> "foo", "otherEnv" -> "bar")), + source = CodeSource( + runtime = "java11", + codeUri = "/somewhere/pet-api.jar", + "pet.api.Handler::handleRequest", + environment = Map("myEnv" -> "foo", "otherEnv" -> "bar") + ), memorySize = 1024 ) From e8c0090f0b55af5e6a16cf04cbb86364c4112c9a Mon Sep 17 00:00:00 2001 From: scala-steward Date: Tue, 24 Jan 2023 00:05:45 +0000 Subject: [PATCH 104/120] Add 'Reformat with scalafmt 3.7.1' to .git-blame-ignore-revs --- .git-blame-ignore-revs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 4b0a2bb67a..6783074210 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -6,3 +6,6 @@ b2c641dce625096839305c65db550ac11c1be0e1 # Scala Steward: Reformat with scalafmt 3.6.1 9ed1c7512fcebb3d74089c275f9a66f9821a0060 + +# Scala Steward: Reformat with scalafmt 3.7.1 +5629d39bd05c54bc957996ca4bf46c4bab1cbd67 From 86c0b33027474120c3d31ba476b3f649f7fe7014 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Wed, 25 Jan 2023 00:16:38 +0000 Subject: [PATCH 105/120] Update play-json to 2.9.4 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index d8eb840f42..dc5ee5a737 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -14,7 +14,7 @@ object Versions { val akkaStreams = "2.6.20" val swaggerUi = "4.15.5" val upickle = "2.0.0" - val playJson = "2.9.3" + val playJson = "2.9.4" val finatra = "22.12.0" val catbird = "21.12.0" val json4s = "4.0.6" From 5e93553c33530b9c541ae338795088a0d4576bbd Mon Sep 17 00:00:00 2001 From: scala-steward Date: Wed, 25 Jan 2023 00:16:53 +0000 Subject: [PATCH 106/120] Revert commit(s) 2add9cbec --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index 4f026a7854..637fd45521 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -51,7 +51,7 @@ object Versions { val decline = "2.4.1" val quicklens = "1.9.0" val openTelemetry = "1.22.0" - val mockServer = "5.15.0" + val mockServer = "5.14.0" val dogstatsdClient = "4.1.0" val nettyAll = "4.1.86.Final" } From a7836359ad7a9e7b11386607e525203bf331f912 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Wed, 25 Jan 2023 00:16:57 +0000 Subject: [PATCH 107/120] Update mockserver-netty to 5.15.0 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index d8eb840f42..db90f2ddb5 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -51,7 +51,7 @@ object Versions { val decline = "2.4.1" val quicklens = "1.9.0" val openTelemetry = "1.22.0" - val mockServer = "5.14.0" + val mockServer = "5.15.0" val dogstatsdClient = "4.2.0" val nettyAll = "4.1.86.Final" } From 4037c2fc2c11d04ec201e22f40a024565223b764 Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 26 Jan 2023 09:51:02 +0100 Subject: [PATCH 108/120] Try compiling in a java 8-compatible way --- build.sbt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index bbf9959598..11094deb70 100644 --- a/build.sbt +++ b/build.sbt @@ -101,7 +101,13 @@ val enableMimaSettings = Seq( val commonJvmSettings: Seq[Def.Setting[_]] = commonSettings ++ Seq( Compile / unmanagedSourceDirectories ++= versionedScalaJvmSourceDirectories((Compile / sourceDirectory).value, scalaVersion.value), Test / unmanagedSourceDirectories ++= versionedScalaJvmSourceDirectories((Test / sourceDirectory).value, scalaVersion.value), - Test / testOptions += Tests.Argument("-oD") // js has other options which conflict with timings + Test / testOptions += Tests.Argument("-oD"), // js has other options which conflict with timings + scalacOptions ++= { + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, _)) => Seq("-target:jvm-1.8") // some users are on java 8 + case _ => Seq.empty[String] + } + } ) // run JS tests inside Gecko, due to jsdom not supporting fetch and to avoid having to install node @@ -528,10 +534,10 @@ lazy val enumeratum: ProjectMatrix = (projectMatrix in file("integrations/enumer "com.beachape" %%% "enumeratum" % Versions.enumeratum, scalaTest.value % Test ), - Test/scalacOptions ++= { + Test / scalacOptions ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((3, _)) => Seq("-Yretain-trees") - case _ => Seq() + case _ => Seq() } } ) From f4cb046ba101fb3ea7cb00c457bafda97aa2a8a7 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Fri, 27 Jan 2023 00:14:56 +0000 Subject: [PATCH 109/120] Update client3:akka-http-backend, ... to 3.8.9 --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index d8eb840f42..26b05800a5 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -6,7 +6,7 @@ object Versions { val circe = "0.14.3" val circeGenericExtras = "0.14.3" val circeYaml = "0.14.2" - val sttp = "3.8.8" + val sttp = "3.8.9" val sttpModel = "1.5.4" val sttpShared = "1.3.12" val sttpApispec = "0.3.2" From 93ca6a5bb9044057804a40eaaf7a00f74f032856 Mon Sep 17 00:00:00 2001 From: frekw Date: Fri, 27 Jan 2023 10:16:50 +0100 Subject: [PATCH 110/120] upgrade to zio-http 0.0.4 --- build.sbt | 5 +- .../server/ziohttp/ZioHttpInterpreter.scala | 47 +++++++------- .../server/ziohttp/ZioHttpServerTest.scala | 9 +-- .../ZioHttpTestServerInterpreter.scala | 61 +++++-------------- 4 files changed, 46 insertions(+), 76 deletions(-) diff --git a/build.sbt b/build.sbt index 11094deb70..bb3e83f200 100644 --- a/build.sbt +++ b/build.sbt @@ -19,7 +19,6 @@ val scala2_13 = "2.13.10" val scala3 = "3.2.1" val scala2Versions = List(scala2_12, scala2_13) -val scala2_13and3Versions = List(scala2_13, scala3) val scala2And3Versions = scala2Versions ++ List(scala3) val codegenScalaVersions = List(scala2_12) val examplesScalaVersions = List(scala2_13) @@ -1360,9 +1359,9 @@ lazy val zioHttpServer: ProjectMatrix = (projectMatrix in file("server/zio-http- .settings(commonJvmSettings) .settings( name := "tapir-zio-http-server", - libraryDependencies ++= Seq("dev.zio" %% "zio-interop-cats" % Versions.zioInteropCats % Test, "dev.zio" %% "zio-http" % "0.0.3") + libraryDependencies ++= Seq("dev.zio" %% "zio-interop-cats" % Versions.zioInteropCats % Test, "dev.zio" %% "zio-http" % "0.0.4") ) - .jvmPlatform(scalaVersions = scala2_13and3Versions) + .jvmPlatform(scalaVersions = scala2And3Versions) .dependsOn(serverCore, zio, serverTests % Test) // serverless 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 bd43d1d580..a3c61dfcec 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 @@ -8,12 +8,11 @@ import sttp.tapir.server.interceptor.RequestResult import sttp.tapir.server.interceptor.reject.RejectInterceptor import sttp.tapir.server.interpreter.{FilterServerEndpoints, ServerInterpreter} import sttp.tapir.ztapir._ -import zio.http.{Body, Http, Request, Response} +import zio.http.{Body, Handler, Http, Request, Response} import zio.http.model.{Status, Header => ZioHttpHeader, Headers => ZioHttpHeaders} import zio._ trait ZioHttpInterpreter[R] { - def zioHttpServerOptions: ZioHttpServerOptions[R] = ZioHttpServerOptions.default def toHttp[R2](se: ZServerEndpoint[R2, ZioStreams]): Http[R & R2, Throwable, Request, Response] = @@ -33,30 +32,34 @@ trait ZioHttpInterpreter[R] { zioHttpServerOptions.deleteFile ) - Http.collectHttp[Request] { case req => - Http.fromZIO { + Http + .fromOptionalHandlerZIO[Request] { req => interpreter .apply(ZioHttpServerRequest(req)) - .map { - case RequestResult.Response(resp) => - val baseHeaders = resp.headers.groupBy(_.name).flatMap(sttpToZioHttpHeader).toList - val allHeaders = resp.body match { - case Some((_, Some(contentLength))) if resp.contentLength.isEmpty => - ZioHttpHeader(HeaderNames.ContentLength, contentLength.toString) :: baseHeaders - case _ => baseHeaders - } + .foldZIO( + error => ZIO.fail(Some(error)), + { + case RequestResult.Response(resp) => + val baseHeaders = resp.headers.groupBy(_.name).flatMap(sttpToZioHttpHeader).toList + val allHeaders = resp.body match { + case Some((_, Some(contentLength))) if resp.contentLength.isEmpty => + ZioHttpHeader(HeaderNames.ContentLength, contentLength.toString) :: baseHeaders + case _ => baseHeaders + } - Http.succeed( - Response( - status = Status.fromHttpResponseStatus(HttpResponseStatus.valueOf(resp.code.code)), - headers = ZioHttpHeaders(allHeaders), - body = resp.body.map { case (stream, _) => Body.fromStream(stream) }.getOrElse(Body.empty) + ZIO.succeed( + Handler.response( + Response( + status = Status.fromHttpResponseStatus(HttpResponseStatus.valueOf(resp.code.code)), + headers = ZioHttpHeaders(allHeaders), + body = resp.body.map { case (stream, _) => Body.fromStream(stream) }.getOrElse(Body.empty) + ) + ) ) - ) - case RequestResult.Failure(_) => Http.empty - } - }.flatten - } + case RequestResult.Failure(_) => ZIO.fail(None) + } + ) + } } private def sttpToZioHttpHeader(hl: (String, Seq[SttpHeader])): List[ZioHttpHeader] = { 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 c246ceefb0..0bda79a2f8 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 @@ -30,17 +30,18 @@ class ZioHttpServerTest extends TestSuite { (channelConfig >>> ChannelFactories.Server.fromConfig) ++ (eventConfig >>> EventLoopGroups.fromConfig) }.build) .map { nettyDeps => - val eventLoopGroup = nettyDeps.get[zio.http.service.EventLoopGroup] - val channelFactory = nettyDeps.get[zio.http.service.ServerChannelFactory] + val eventLoopGroup = ZLayer.succeed(nettyDeps.get[zio.http.service.EventLoopGroup]) + val channelFactory = ZLayer.succeed(nettyDeps.get[zio.http.service.ServerChannelFactory]) val interpreter = new ZioHttpTestServerInterpreter(eventLoopGroup, channelFactory) val createServerTest = new DefaultCreateServerTest(backend, interpreter) def additionalTests(): List[Test] = List( // https://github.com/softwaremill/tapir/issues/1914 - Test("zio http route can be used as a function") { + Test("zio http route can be called with runZIO") { val ep = endpoint.get.in("p1").out(stringBody).zServerLogic[Any](_ => ZIO.succeed("response")) val route = ZioHttpInterpreter().toHttp(ep) - val test: UIO[Assertion] = route(Request.get(url = URL.apply(Path.empty / "p1"))) + val test: UIO[Assertion] = route + .runZIO(Request.get(url = URL.apply(Path.empty / "p1"))) .flatMap(response => response.body.asString) .map(_ shouldBe "response") .catchAll(_ => ZIO.succeed(fail("Unable to extract body from Http response"))) diff --git a/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpTestServerInterpreter.scala b/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpTestServerInterpreter.scala index c44d82056f..8fb79bb2fd 100644 --- a/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpTestServerInterpreter.scala +++ b/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpTestServerInterpreter.scala @@ -8,12 +8,14 @@ import sttp.tapir.server.tests.TestServerInterpreter import sttp.tapir.tests.Port import zio._ import zio.http.netty.server.NettyDriver -import zio.http.service.ServerChannelFactory import zio.http._ import zio.interop.catz._ -class ZioHttpTestServerInterpreter(eventLoopGroup: zio.http.service.EventLoopGroup, channelFactory: zio.http.service.ServerChannelFactory)( - implicit trace: Trace +class ZioHttpTestServerInterpreter( + eventLoopGroup: ZLayer[Any, Nothing, zio.http.service.EventLoopGroup], + channelFactory: ZLayer[Any, Nothing, zio.http.service.ServerChannelFactory] +)(implicit + trace: Trace ) extends TestServerInterpreter[Task, ZioStreams, ZioHttpServerOptions[Any], Http[Any, Throwable, Request, Response]] { override def route(es: List[ServerEndpoint[ZioStreams, Task]], interceptors: Interceptors): Http[Any, Throwable, Request, Response] = { @@ -24,53 +26,18 @@ class ZioHttpTestServerInterpreter(eventLoopGroup: zio.http.service.EventLoopGro override def server(routes: NonEmptyList[Http[Any, Throwable, Request, Response]]): Resource[IO, Port] = { implicit val r: Runtime[Any] = Runtime.default - val makeNettyDriver = { - import zio.http.netty.server._ - import io.netty.bootstrap.ServerBootstrap - import io.netty.channel._ - import io.netty.util.ResourceLeakDetector - import zio._ - import zio.http.netty._ - import zio.http.service.ServerTime - import zio.http.{Driver, Http, HttpApp, Server, ServerConfig} - - import java.net.InetSocketAddress - import java.util.concurrent.atomic.AtomicReference - - type ErrorCallbackRef = AtomicReference[Option[Server.ErrorCallback]] - type AppRef = AtomicReference[(HttpApp[Any, Throwable], ZEnvironment[Any])] - type EnvRef = AtomicReference[ZEnvironment[Any]] - - val app = ZLayer.succeed( - new AtomicReference[(HttpApp[Any, Throwable], ZEnvironment[Any])]((Http.empty, ZEnvironment.empty)) - ) - val ecb = ZLayer.succeed(new AtomicReference[Option[Server.ErrorCallback]](Option.empty)) - val time = ZLayer.succeed(ServerTime.make(1000.millis)) - - val nettyRuntime = NettyRuntime.usingSharedThreadPool - val serverChannelInitializer = ServerChannelInitializer.layer - val serverInboundHandler = ServerInboundHandler.layer - - val serverLayers = app ++ - ZLayer.succeed(channelFactory) ++ - ( - ( - (time ++ app ++ ecb) ++ - (ZLayer.succeed(eventLoopGroup) >>> nettyRuntime) >>> serverInboundHandler - ) >>> serverChannelInitializer - ) ++ - ecb ++ - ZLayer.succeed(eventLoopGroup) - - NettyDriver.make.provideSomeLayer[ServerConfig with Scope](serverLayers) - } - val effect: ZIO[Scope, Throwable, Int] = (for { - driver <- makeNettyDriver + driver <- ZIO.service[Driver] port <- driver.start(trace) - _ <- driver.addApp[Any](routes.toList.reduce(_ ++ _), ZEnvironment()) - } yield port).provideSome[Scope](ServerConfig.live(ServerConfig.default.port(0).objectAggregator(1000000))) + _ <- driver.addApp[Any](routes.toList.reduce(_ ++ _).withDefaultErrorResponse, ZEnvironment()) + } yield port) + .provideSome[Scope]( + NettyDriver.manual, + eventLoopGroup, + channelFactory, + ServerConfig.live(ServerConfig.default.port(0).objectAggregator(1000000)) + ) Resource.scoped[IO, Any, Int](effect) } From 959f0efb762fa41711cab9c0feb6601172b1510b Mon Sep 17 00:00:00 2001 From: frekw Date: Fri, 27 Jan 2023 15:37:10 +0100 Subject: [PATCH 111/120] toHttp -> toApp --- .../examples/HelloWorldZioHttpServer.scala | 19 +++++++++++-------- .../examples/ZioExampleZioHttpServer.scala | 10 +++++----- .../observability/ZioMetricsExample.scala | 6 +++--- .../examples/openapi/RedocZioHttpServer.scala | 10 +++++----- .../streaming/StreamingZioHttpServer.scala | 6 +++--- .../server/ziohttp/ZioHttpInterpreter.scala | 9 +++++---- 6 files changed, 32 insertions(+), 28 deletions(-) diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala index 0d90106304..3f5b2301b1 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala @@ -5,7 +5,7 @@ import sttp.tapir.ztapir._ import sttp.tapir.generic.auto._ import sttp.tapir.json.zio._ import sttp.tapir.server.ziohttp.ZioHttpInterpreter -import zio.http.HttpApp +import zio.http.App import zio.http.{Server, ServerConfig} import zio._ import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} @@ -32,14 +32,17 @@ object HelloWorldZioHttpServer extends ZIOAppDefault { .out(jsonBody[AddResult]) // converting the endpoint descriptions to the Http type - val app: HttpApp[Any, Throwable] = - ZioHttpInterpreter().toHttp(helloWorld.zServerLogic(name => ZIO.succeed(s"Hello, $name!"))) <> - ZioHttpInterpreter().toHttp(add.zServerLogic { case (x, y) => ZIO.succeed(AddResult(x, y, x + y)) }) + val app: App[Any] = + ZioHttpInterpreter().toApp(helloWorld.zServerLogic(name => ZIO.succeed(s"Hello, $name!"))) ++ + ZioHttpInterpreter().toApp(add.zServerLogic { case (x, y) => ZIO.succeed(AddResult(x, y, x + y)) }) // starting the server override def run = - Server.serve(app).provide( - ServerConfig.live(ServerConfig.default.port(8090)), - Server.live, - ).exitCode + Server + .serve(app) + .provide( + ServerConfig.live(ServerConfig.default.port(8090)), + Server.live + ) + .exitCode } diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala index c60176d6f9..3368bb1135 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala @@ -7,7 +7,7 @@ import sttp.tapir.json.circe._ import sttp.tapir.server.ziohttp.ZioHttpInterpreter import sttp.tapir.swagger.bundle.SwaggerInterpreter import sttp.tapir.ztapir._ -import zio.http.HttpApp +import zio.http.App import zio.http.{Server, ServerConfig} import zio.{ExitCode, Task, URIO, ZIO, ZIOAppDefault} @@ -18,8 +18,8 @@ object ZioExampleZioHttpServer extends ZIOAppDefault { val petEndpoint: PublicEndpoint[Int, String, Pet, Any] = endpoint.get.in("pet" / path[Int]("petId")).errorOut(stringBody).out(jsonBody[Pet]) - val petRoutes: HttpApp[Any, Throwable] = - ZioHttpInterpreter().toHttp( + val petRoutes: App[Any] = + ZioHttpInterpreter().toApp( petEndpoint.zServerLogic(petId => if (petId == 35) ZIO.succeed(Pet("Tapirus terrestris", "https://en.wikipedia.org/wiki/Tapir")) else ZIO.fail("Unknown pet id") @@ -39,14 +39,14 @@ object ZioExampleZioHttpServer extends ZIOAppDefault { val swaggerEndpoints: List[ZServerEndpoint[Any, Any]] = SwaggerInterpreter().fromEndpoints[Task](List(petEndpoint), "Our pets", "1.0") // Starting the server - val routes: HttpApp[Any, Throwable] = ZioHttpInterpreter().toHttp(List(petServerEndpoint) ++ swaggerEndpoints) + val routes: App[Any] = ZioHttpInterpreter().toApp(List(petServerEndpoint) ++ swaggerEndpoints) override def run: URIO[Any, ExitCode] = Server .serve(routes) .provide( ServerConfig.live(ServerConfig.default.port(8080)), - Server.live, + Server.live ) .exitCode } diff --git a/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala b/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala index 3ed4dbf38b..95e0af43f9 100644 --- a/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala @@ -3,7 +3,7 @@ import sttp.tapir.server.interceptor.metrics.MetricsRequestInterceptor import sttp.tapir.server.metrics.zio.ZioMetrics import sttp.tapir.server.ziohttp.{ZioHttpInterpreter, ZioHttpServerOptions} import sttp.tapir.ztapir.ZServerEndpoint -import zio.http.HttpApp +import zio.http.App import zio.http.{Server, ServerConfig} import zio.{Task, ZIO, _} @@ -29,7 +29,7 @@ object ZioMetricsExample extends ZIOAppDefault { override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] = { val serverOptions: ZioHttpServerOptions[Any] = ZioHttpServerOptions.customiseInterceptors.metricsInterceptor(metricsInterceptor).options - val app: HttpApp[Any, Throwable] = ZioHttpInterpreter(serverOptions).toHttp(all) + val app: App[Any] = ZioHttpInterpreter(serverOptions).toApp(all) val port = sys.env.get("http.port").map(_.toInt).getOrElse(8080) @@ -40,7 +40,7 @@ object ZioMetricsExample extends ZIOAppDefault { } yield serverPort) .provide( ServerConfig.live(ServerConfig.default.port(port)), - Server.live, + Server.live ) .exitCode } diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala index 71844e3588..9433323e96 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala @@ -6,7 +6,7 @@ import sttp.tapir.json.circe._ import sttp.tapir.redoc.bundle.RedocInterpreter import sttp.tapir.server.ziohttp.ZioHttpInterpreter import sttp.tapir.ztapir._ -import zio.http.HttpApp +import zio.http.App import zio.http.{Server, ServerConfig} import zio.Console.{printLine, readLine} import zio.{Task, ZIO, ZIOAppDefault} @@ -20,10 +20,10 @@ object RedocZioHttpServer extends ZIOAppDefault { else ZIO.fail("Unknown pet id") } - val petRoutes: HttpApp[Any, Throwable] = ZioHttpInterpreter().toHttp(petEndpoint) + val petRoutes: App[Any] = ZioHttpInterpreter().toApp(petEndpoint) - val redocRoutes: HttpApp[Any, Throwable] = - ZioHttpInterpreter().toHttp(RedocInterpreter().fromServerEndpoints[Task](List(petEndpoint), "Our pets", "1.0")) + val redocRoutes: App[Any] = + ZioHttpInterpreter().toApp(RedocInterpreter().fromServerEndpoints[Task](List(petEndpoint), "Our pets", "1.0")) override def run = { printLine("Go to: http://localhost:8080/docs") *> @@ -32,7 +32,7 @@ object RedocZioHttpServer extends ZIOAppDefault { .serve(petRoutes ++ redocRoutes) .provide( ServerConfig.live(ServerConfig.default.port(8080)), - Server.live, + Server.live ) .fork .flatMap { fiber => diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala index f7b595f2a0..50c74dc016 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala @@ -5,7 +5,7 @@ import sttp.model.HeaderNames import sttp.tapir.{CodecFormat, PublicEndpoint} import sttp.tapir.ztapir._ import sttp.tapir.server.ziohttp.ZioHttpInterpreter -import zio.http.HttpApp +import zio.http.App import zio.http.{Server, ServerConfig} import zio.{ExitCode, Schedule, URIO, ZIO, ZIOAppDefault} import zio.stream._ @@ -37,7 +37,7 @@ object StreamingZioHttpServer extends ZIOAppDefault { ZIO.succeed((size, stream)) } - val routes: HttpApp[Any, Throwable] = ZioHttpInterpreter().toHttp(streamingServerEndpoint) + val routes: App[Any] = ZioHttpInterpreter().toApp(streamingServerEndpoint) // Test using: curl http://localhost:8080/receive override def run: URIO[Any, ExitCode] = @@ -45,7 +45,7 @@ object StreamingZioHttpServer extends ZIOAppDefault { .serve(routes) .provide( ServerConfig.live(ServerConfig.default.port(8080)), - Server.live, + Server.live ) .exitCode } 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 a3c61dfcec..4ec015143b 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 @@ -8,17 +8,17 @@ import sttp.tapir.server.interceptor.RequestResult import sttp.tapir.server.interceptor.reject.RejectInterceptor import sttp.tapir.server.interpreter.{FilterServerEndpoints, ServerInterpreter} import sttp.tapir.ztapir._ -import zio.http.{Body, Handler, Http, Request, Response} +import zio.http.{App, Body, Handler, Http, Request, Response} import zio.http.model.{Status, Header => ZioHttpHeader, Headers => ZioHttpHeaders} import zio._ trait ZioHttpInterpreter[R] { def zioHttpServerOptions: ZioHttpServerOptions[R] = ZioHttpServerOptions.default - def toHttp[R2](se: ZServerEndpoint[R2, ZioStreams]): Http[R & R2, Throwable, Request, Response] = - toHttp(List(se)) + def toApp[R2](se: ZServerEndpoint[R2, ZioStreams]): App[R & R2] = + toApp(List(se)) - def toHttp[R2](ses: List[ZServerEndpoint[R2, ZioStreams]]): Http[R & R2, Throwable, Request, Response] = { + def toApp[R2](ses: List[ZServerEndpoint[R2, ZioStreams]]): App[R & R2] = { implicit val bodyListener: ZioHttpBodyListener[R & R2] = new ZioHttpBodyListener[R & R2] implicit val monadError: MonadError[RIO[R & R2, *]] = new RIOMonadError[R & R2] val widenedSes = ses.map(_.widen[R & R2]) @@ -60,6 +60,7 @@ trait ZioHttpInterpreter[R] { } ) } + .withDefaultErrorResponse } private def sttpToZioHttpHeader(hl: (String, Seq[SttpHeader])): List[ZioHttpHeader] = { From a90712a54757bcf660ef3518f607f20ea6ab9f32 Mon Sep 17 00:00:00 2001 From: frekw Date: Fri, 27 Jan 2023 15:54:07 +0100 Subject: [PATCH 112/120] fix docs --- doc/server/ziohttp.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/server/ziohttp.md b/doc/server/ziohttp.md index 61a25c0b46..3204fec86a 100644 --- a/doc/server/ziohttp.md +++ b/doc/server/ziohttp.md @@ -46,7 +46,7 @@ example: import sttp.tapir.PublicEndpoint import sttp.tapir.ztapir._ import sttp.tapir.server.ziohttp.ZioHttpInterpreter -import zio.http.{Http, Request, Response} +import zio.http.{App, Request, Response} import zio._ def countCharacters(s: String): ZIO[Any, Nothing, Int] = @@ -55,8 +55,8 @@ def countCharacters(s: String): ZIO[Any, Nothing, Int] = val countCharactersEndpoint: PublicEndpoint[String, Unit, Int, Any] = endpoint.in(stringBody).out(plainBody[Int]) -val countCharactersHttp: Http[Any, Throwable, Request, Response] = - ZioHttpInterpreter().toHttp(countCharactersEndpoint.zServerLogic(countCharacters)) +val countCharactersHttp: App[Any] = + ZioHttpInterpreter().toApp(countCharactersEndpoint.zServerLogic(countCharacters)) ``` ## Server logic From 3d109012b38b5beeb78fde27209a2f05b02983dd Mon Sep 17 00:00:00 2001 From: frekw Date: Fri, 27 Jan 2023 16:25:51 +0100 Subject: [PATCH 113/120] disable scala 2.12 again --- build.sbt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index bb3e83f200..262a1182d6 100644 --- a/build.sbt +++ b/build.sbt @@ -19,6 +19,7 @@ val scala2_13 = "2.13.10" val scala3 = "3.2.1" val scala2Versions = List(scala2_12, scala2_13) +val scala2_13and3Versions = List(scala2_13, scala3) val scala2And3Versions = scala2Versions ++ List(scala3) val codegenScalaVersions = List(scala2_12) val examplesScalaVersions = List(scala2_13) @@ -1361,7 +1362,7 @@ lazy val zioHttpServer: ProjectMatrix = (projectMatrix in file("server/zio-http- name := "tapir-zio-http-server", libraryDependencies ++= Seq("dev.zio" %% "zio-interop-cats" % Versions.zioInteropCats % Test, "dev.zio" %% "zio-http" % "0.0.4") ) - .jvmPlatform(scalaVersions = scala2And3Versions) + .jvmPlatform(scalaVersions = scala2_13and3Versions) .dependsOn(serverCore, zio, serverTests % Test) // serverless From 1b35ed004d217aa438fea5fa8bf276fdaf20139b Mon Sep 17 00:00:00 2001 From: frekw Date: Fri, 27 Jan 2023 16:26:01 +0100 Subject: [PATCH 114/120] fix scala 3 --- .../server/ziohttp/ZioHttpCompositionTest.scala | 13 +++++++++---- .../tapir/server/ziohttp/ZioHttpServerTest.scala | 2 +- .../ziohttp/ZioHttpTestServerInterpreter.scala | 8 ++++---- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpCompositionTest.scala b/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpCompositionTest.scala index b7f1853fed..86aa940e39 100644 --- a/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpCompositionTest.scala +++ b/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpCompositionTest.scala @@ -12,7 +12,12 @@ import zio.http.model._ import zio.{Task, ZIO} class ZioHttpCompositionTest( - createServerTest: CreateServerTest[Task, Any, ZioHttpServerOptions[Any], Http[Any, Throwable, zio.http.Request, zio.http.Response]] + createServerTest: CreateServerTest[ + Task, + Any, + ZioHttpServerOptions[Any], + Http[Any, zio.http.Response, zio.http.Request, zio.http.Response] + ] ) { import createServerTest._ @@ -22,11 +27,11 @@ class ZioHttpCompositionTest( val ep1 = endpoint.get.in("p1").zServerLogic[Any](_ => ZIO.unit) val ep3 = endpoint.get.in("p3").zServerLogic[Any](_ => ZIO.fail(new RuntimeException("boom"))) - val route1: RHttpApp[Any] = ZioHttpInterpreter().toHttp(ep1) - val route2: RHttpApp[Any] = Http.collect { case Method.GET -> !! / "p2" => + val route1: App[Any] = ZioHttpInterpreter().toApp(ep1) + val route2: App[Any] = Http.collect { case Method.GET -> !! / "p2" => zio.http.Response.ok } - val route3: RHttpApp[Any] = ZioHttpInterpreter().toHttp(ep3) + val route3: App[Any] = ZioHttpInterpreter().toApp(ep3) NonEmptyList.of(route3, route1, route2) } 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 0bda79a2f8..f4ff647c47 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 @@ -39,7 +39,7 @@ class ZioHttpServerTest extends TestSuite { // https://github.com/softwaremill/tapir/issues/1914 Test("zio http route can be called with runZIO") { val ep = endpoint.get.in("p1").out(stringBody).zServerLogic[Any](_ => ZIO.succeed("response")) - val route = ZioHttpInterpreter().toHttp(ep) + val route = ZioHttpInterpreter().toApp(ep) val test: UIO[Assertion] = route .runZIO(Request.get(url = URL.apply(Path.empty / "p1"))) .flatMap(response => response.body.asString) diff --git a/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpTestServerInterpreter.scala b/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpTestServerInterpreter.scala index 8fb79bb2fd..d5d6543ddb 100644 --- a/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpTestServerInterpreter.scala +++ b/server/zio-http-server/src/test/scala/sttp/tapir/server/ziohttp/ZioHttpTestServerInterpreter.scala @@ -16,14 +16,14 @@ class ZioHttpTestServerInterpreter( channelFactory: ZLayer[Any, Nothing, zio.http.service.ServerChannelFactory] )(implicit trace: Trace -) extends TestServerInterpreter[Task, ZioStreams, ZioHttpServerOptions[Any], Http[Any, Throwable, Request, Response]] { +) extends TestServerInterpreter[Task, ZioStreams, ZioHttpServerOptions[Any], Http[Any, Response, Request, Response]] { - override def route(es: List[ServerEndpoint[ZioStreams, Task]], interceptors: Interceptors): Http[Any, Throwable, Request, Response] = { + override def route(es: List[ServerEndpoint[ZioStreams, Task]], interceptors: Interceptors): Http[Any, Response, Request, Response] = { val serverOptions: ZioHttpServerOptions[Any] = interceptors(ZioHttpServerOptions.customiseInterceptors).options - ZioHttpInterpreter(serverOptions).toHttp(es) + ZioHttpInterpreter(serverOptions).toApp(es) } - override def server(routes: NonEmptyList[Http[Any, Throwable, Request, Response]]): Resource[IO, Port] = { + override def server(routes: NonEmptyList[Http[Any, Response, Request, Response]]): Resource[IO, Port] = { implicit val r: Runtime[Any] = Runtime.default val effect: ZIO[Scope, Throwable, Int] = From f86a13f2be2c56597241caa0b2ed71cc9b553b4e Mon Sep 17 00:00:00 2001 From: adamw Date: Fri, 27 Jan 2023 21:21:57 +0100 Subject: [PATCH 115/120] Release 1.2.7 --- README.md | 2 +- generated-doc/out/client/http4s.md | 2 +- generated-doc/out/client/play.md | 2 +- generated-doc/out/client/sttp.md | 4 ++-- generated-doc/out/docs/asyncapi.md | 2 +- generated-doc/out/docs/openapi.md | 12 ++++++------ generated-doc/out/endpoint/integrations.md | 14 +++++++------- generated-doc/out/endpoint/json.md | 16 ++++++++-------- .../out/generator/sbt-openapi-codegen.md | 2 +- generated-doc/out/quickstart.md | 2 +- generated-doc/out/server/akkahttp.md | 4 ++-- generated-doc/out/server/armeria.md | 8 ++++---- generated-doc/out/server/aws.md | 6 +++--- generated-doc/out/server/finatra.md | 4 ++-- generated-doc/out/server/http4s.md | 2 +- generated-doc/out/server/netty.md | 8 ++++---- generated-doc/out/server/observability.md | 8 ++++---- generated-doc/out/server/play.md | 6 +++--- generated-doc/out/server/vertx.md | 8 ++++---- generated-doc/out/server/zio-http4s.md | 6 +++--- generated-doc/out/server/ziohttp.md | 10 +++++----- generated-doc/out/testing.md | 10 +++++----- 22 files changed, 69 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 5dfec0beab..01c6ab5c1a 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ tapir documentation is available at [tapir.softwaremill.com](http://tapir.softwa Add the following dependency: ```sbt -"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.7" ``` Then, import: diff --git a/generated-doc/out/client/http4s.md b/generated-doc/out/client/http4s.md index 1cf49e8967..36815188cc 100644 --- a/generated-doc/out/client/http4s.md +++ b/generated-doc/out/client/http4s.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-http4s-client" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-client" % "1.2.7" ``` To interpret an endpoint definition as an `org.http4s.Request[F]`, import: diff --git a/generated-doc/out/client/play.md b/generated-doc/out/client/play.md index 672837a4c1..f5ea308d07 100644 --- a/generated-doc/out/client/play.md +++ b/generated-doc/out/client/play.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-play-client" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-play-client" % "1.2.7" ``` To make requests using an endpoint definition using the [play client](https://github.com/playframework/play-ws), import: diff --git a/generated-doc/out/client/sttp.md b/generated-doc/out/client/sttp.md index 980364afea..2d3e58df3b 100644 --- a/generated-doc/out/client/sttp.md +++ b/generated-doc/out/client/sttp.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % "1.2.7" ``` To make requests using an endpoint definition using the [sttp client](https://github.com/softwaremill/sttp), import: @@ -102,7 +102,7 @@ In this case add the following dependencies (note the [`%%%`](https://www.scala- instead of the usual `%%`): ```scala -"com.softwaremill.sttp.tapir" %%% "tapir-sttp-client" % "1.2.6" +"com.softwaremill.sttp.tapir" %%% "tapir-sttp-client" % "1.2.7" "io.github.cquiroz" %%% "scala-java-time" % "2.2.0" // implementations of java.time classes for Scala.JS ``` diff --git a/generated-doc/out/docs/asyncapi.md b/generated-doc/out/docs/asyncapi.md index 78b8200602..1d17d20221 100644 --- a/generated-doc/out/docs/asyncapi.md +++ b/generated-doc/out/docs/asyncapi.md @@ -3,7 +3,7 @@ To use, add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-asyncapi-docs" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-asyncapi-docs" % "1.2.7" "com.softwaremill.sttp.apispec" %% "asyncapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` diff --git a/generated-doc/out/docs/openapi.md b/generated-doc/out/docs/openapi.md index 92f792b078..2348e1d95c 100644 --- a/generated-doc/out/docs/openapi.md +++ b/generated-doc/out/docs/openapi.md @@ -13,7 +13,7 @@ these steps can be done separately, giving you complete control over the process To generate OpenAPI documentation and expose it using the Swagger UI in a single step, first add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % "1.2.7" ``` Then, you can interpret a list of endpoints using `SwaggerInterpreter`. The result will be a list of file-serving @@ -55,7 +55,7 @@ for details. Similarly as above, you'll need the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-redoc-bundle" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-redoc-bundle" % "1.2.7" ``` And the server endpoints can be generated using the `sttp.tapir.redoc.bundle.RedocInterpreter` class. @@ -65,7 +65,7 @@ And the server endpoints can be generated using the `sttp.tapir.redoc.bundle.Red To generate the docs in the OpenAPI yaml format, add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.7" "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` @@ -133,7 +133,7 @@ For example, generating the OpenAPI 3.1.0 YAML string can be achieved by perform Firstly add dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.2.7" "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` @@ -164,12 +164,12 @@ The modules `tapir-swagger-ui` and `tapir-redoc` contain server endpoint definit yaml format, will expose it using the given context path. To use, add as a dependency either `tapir-swagger-ui`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui" % "1.2.7" ``` or `tapir-redoc`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-redoc" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-redoc" % "1.2.7" ``` Then, you'll need to pass the server endpoints to your server interpreter. For example, using akka-http: diff --git a/generated-doc/out/endpoint/integrations.md b/generated-doc/out/endpoint/integrations.md index f47548632c..78301cb398 100644 --- a/generated-doc/out/endpoint/integrations.md +++ b/generated-doc/out/endpoint/integrations.md @@ -14,7 +14,7 @@ The `tapir-cats` module contains additional instances for some [cats](https://ty datatypes as well as additional syntax: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-cats" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-cats" % "1.2.7" ``` - `import sttp.tapir.integ.cats.codec._` - brings schema, validator and codec instances @@ -26,7 +26,7 @@ If you use [refined](https://github.com/fthomas/refined), the `tapir-refined` mo validators for `T Refined P` as long as a codec for `T` already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-refined" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-refined" % "1.2.7" ``` You'll need to extend the `sttp.tapir.codec.refined.TapirCodecRefined` @@ -47,7 +47,7 @@ The `tapir-enumeratum` module provides schemas, validators and codecs for [Enume enumerations. To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % "1.2.7" ``` Then, `import sttp.tapir.codec.enumeratum._`, or extends the `sttp.tapir.codec.enumeratum.TapirCodecEnumeratum` trait. @@ -60,7 +60,7 @@ If you use [scala-newtype](https://github.com/estatico/scala-newtype), the `tapi schemas for types with a `@newtype` and `@newsubtype` annotations as long as a codec and schema for its underlying value already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-newtype" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-newtype" % "1.2.7" ``` Then, `import sttp.tapir.codec.newtype._`, or extend the `sttp.tapir.codec.newtype.TapirCodecNewType` trait to bring the implicit values into scope. @@ -71,7 +71,7 @@ If you use [monix newtypes](https://github.com/monix/newtypes), the `tapir-monix schemas for types which extend `NewtypeWrapped` and `NewsubtypeWrapped` annotations as long as a codec and schema for its underlying value already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-monix-newtype" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-monix-newtype" % "1.2.7" ``` Then, `import sttp.tapir.codec.monix.newtype._`, or extend the `sttp.tapir.codec.monix.newtype.TapirCodecMonixNewType` trait to bring the implicit values into scope. @@ -82,7 +82,7 @@ If you use [ZIO Prelude Newtypes](https://zio.github.io/zio-prelude/docs/newtype schemas for types defined using `Newtype` and `Subtype` as long as a codec and a schema for the underlying type already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio-prelude" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-zio-prelude" % "1.2.7" ``` Then, mix in `sttp.tapir.codec.zio.prelude.newtype.TapirNewtypeSupport` into your newtype to bring the implicit values into scope: @@ -121,7 +121,7 @@ For details refer to [derevo documentation](https://github.com/tofu-tf/derevo#in To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-derevo" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-derevo" % "1.2.7" ``` Then you can derive schema for your ADT along with other typeclasses besides ADT declaration itself: diff --git a/generated-doc/out/endpoint/json.md b/generated-doc/out/endpoint/json.md index abc9bb12eb..06b3ccdcd8 100644 --- a/generated-doc/out/endpoint/json.md +++ b/generated-doc/out/endpoint/json.md @@ -45,7 +45,7 @@ stringJsonBody.schema(implicitly[Schema[MyBody]].as[String]) To use [Circe](https://github.com/circe/circe), add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.2.7" ``` Next, import the package (or extend the `TapirJsonCirce` trait, see [MyTapir](../mytapir.md)): @@ -118,7 +118,7 @@ Now the above JSON object will render as To use [µPickle](http://www.lihaoyi.com/upickle/) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-upickle" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-json-upickle" % "1.2.7" ``` Next, import the package (or extend the `TapirJsonuPickle` trait, see [MyTapir](../mytapir.md) and add `TapirJsonuPickle` not `TapirCirceJson`): @@ -153,7 +153,7 @@ For more examples, including making a custom encoder/decoder, see [TapirJsonuPic To use [Play JSON](https://github.com/playframework/play-json) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-play" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-json-play" % "1.2.7" ``` Next, import the package (or extend the `TapirJsonPlay` trait, see [MyTapir](../mytapir.md) and add `TapirJsonPlay` not `TapirCirceJson`): @@ -169,7 +169,7 @@ Play JSON requires `Reads` and `Writes` implicit values in scope for each type y To use [Spray JSON](https://github.com/spray/spray-json) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-spray" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-json-spray" % "1.2.7" ``` Next, import the package (or extend the `TapirJsonSpray` trait, see [MyTapir](../mytapir.md) and add `TapirJsonSpray` not `TapirCirceJson`): @@ -185,7 +185,7 @@ Spray JSON requires a `JsonFormat` implicit value in scope for each type you wan To use [Tethys JSON](https://github.com/tethys-json/tethys) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-tethys" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-json-tethys" % "1.2.7" ``` Next, import the package (or extend the `TapirJsonTethys` trait, see [MyTapir](../mytapir.md) and add `TapirJsonTethys` not `TapirCirceJson`): @@ -201,7 +201,7 @@ Tethys JSON requires `JsonReader` and `JsonWriter` implicit values in scope for To use [Jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "1.2.7" ``` Next, import the package (or extend the `TapirJsonJsoniter` trait, see [MyTapir](../mytapir.md) and add `TapirJsonJsoniter` not `TapirCirceJson`): @@ -217,7 +217,7 @@ Jsoniter Scala requires `JsonValueCodec` implicit value in scope for each type y To use [json4s](https://github.com/json4s/json4s) add the following dependencies to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-json4s" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-json-json4s" % "1.2.7" ``` And one of the implementations: @@ -248,7 +248,7 @@ implicit val formats: Formats = org.json4s.jackson.Serialization.formats(NoTypeH To use [zio-json](https://github.com/zio/zio-json), add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "1.2.7" ``` Next, import the package (or extend the `TapirJsonZio` trait, see [MyTapir](../mytapir.md) and add `TapirJsonZio` instead of `TapirCirceJson`): diff --git a/generated-doc/out/generator/sbt-openapi-codegen.md b/generated-doc/out/generator/sbt-openapi-codegen.md index dcdaf053bc..834a384356 100644 --- a/generated-doc/out/generator/sbt-openapi-codegen.md +++ b/generated-doc/out/generator/sbt-openapi-codegen.md @@ -11,7 +11,7 @@ Add the sbt plugin to the `project/plugins.sbt`: ```scala -addSbtPlugin("com.softwaremill.sttp.tapir" % "sbt-openapi-codegen" % "1.2.6") +addSbtPlugin("com.softwaremill.sttp.tapir" % "sbt-openapi-codegen" % "1.2.7") ``` Enable the plugin for your project in the `build.sbt`: diff --git a/generated-doc/out/quickstart.md b/generated-doc/out/quickstart.md index eb026dced9..14bc365d71 100644 --- a/generated-doc/out/quickstart.md +++ b/generated-doc/out/quickstart.md @@ -3,7 +3,7 @@ To use tapir, add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.2.7" ``` This will import only the core classes needed to create endpoint descriptions. To generate a server or a client, you diff --git a/generated-doc/out/server/akkahttp.md b/generated-doc/out/server/akkahttp.md index 4112000ecd..42591a5170 100644 --- a/generated-doc/out/server/akkahttp.md +++ b/generated-doc/out/server/akkahttp.md @@ -4,14 +4,14 @@ To expose an endpoint as an [akka-http](https://doc.akka.io/docs/akka-http/curre dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.7" ``` This will transitively pull some Akka modules in version 2.6. If you want to force your own Akka version (for example 2.5), use sbt exclusion. Mind the Scala version in artifact name: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.6" exclude("com.typesafe.akka", "akka-stream_2.12") +"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.2.7" exclude("com.typesafe.akka", "akka-stream_2.12") ``` Now import the object: diff --git a/generated-doc/out/server/armeria.md b/generated-doc/out/server/armeria.md index 8e7b067f5c..42d67555ff 100644 --- a/generated-doc/out/server/armeria.md +++ b/generated-doc/out/server/armeria.md @@ -8,7 +8,7 @@ Armeria interpreter can be used with different effect systems (cats-effect, ZIO) Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server" % "1.2.7" ``` and import the object: @@ -75,7 +75,7 @@ Note that Armeria automatically injects an `ExecutionContext` on top of Armeria' Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-cats" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-cats" % "1.2.7" ``` to use this interpreter with Cats Effect typeclasses. @@ -155,9 +155,9 @@ Add the following dependency ```scala // for zio 2: -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio" % "1.2.7" // for zio 1: -"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio1" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-zio1" % "1.2.7" ``` to use this interpreter with ZIO. diff --git a/generated-doc/out/server/aws.md b/generated-doc/out/server/aws.md index 9df5cff57c..76249b6f9a 100644 --- a/generated-doc/out/server/aws.md +++ b/generated-doc/out/server/aws.md @@ -13,7 +13,7 @@ To implement the Lambda function, a server interpreter is available, which takes Currently, only an interpreter integrating with cats-effect is available (`AwsCatsEffectServerInterpreter`). To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-aws-lambda" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-aws-lambda" % "1.2.7" ``` To configure API Gateway and the Lambda function, you can use: @@ -24,8 +24,8 @@ To configure API Gateway and the Lambda function, you can use: Add one of the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-aws-sam" % "1.2.6" -"com.softwaremill.sttp.tapir" %% "tapir-aws-terraform" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-aws-sam" % "1.2.7" +"com.softwaremill.sttp.tapir" %% "tapir-aws-terraform" % "1.2.7" ``` ## Examples diff --git a/generated-doc/out/server/finatra.md b/generated-doc/out/server/finatra.md index 60aabfe5a7..caeb9a5c77 100644 --- a/generated-doc/out/server/finatra.md +++ b/generated-doc/out/server/finatra.md @@ -4,7 +4,7 @@ To expose an endpoint as an [finatra](https://twitter.github.io/finatra/) server dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-finatra-server" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-finatra-server" % "1.2.7" ``` and import the object: @@ -17,7 +17,7 @@ This interpreter supports the twitter `Future`. Or, if you would like to use cats-effect project, you can add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-finatra-server-cats" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-finatra-server-cats" % "1.2.7" ``` and import the object: diff --git a/generated-doc/out/server/http4s.md b/generated-doc/out/server/http4s.md index b567b832ff..0fdeb10a92 100644 --- a/generated-doc/out/server/http4s.md +++ b/generated-doc/out/server/http4s.md @@ -4,7 +4,7 @@ To expose an endpoint as an [http4s](https://http4s.org) server, first add the f dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % "1.2.7" ``` and import the object: diff --git a/generated-doc/out/server/netty.md b/generated-doc/out/server/netty.md index f214b27db2..a9d4090817 100644 --- a/generated-doc/out/server/netty.md +++ b/generated-doc/out/server/netty.md @@ -4,16 +4,16 @@ To expose an endpoint using a [Netty](https://netty.io)-based server, first add ```scala // if you are using Future or just exploring -"com.softwaremill.sttp.tapir" %% "tapir-netty-server" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server" % "1.2.7" // if you are using cats-effect: -"com.softwaremill.sttp.tapir" %% "tapir-netty-server-cats" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-cats" % "1.2.7" // if you are using zio: -"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio" % "1.2.7" // if you are using zio1: -"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio1" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio1" % "1.2.7" ``` Then, use: diff --git a/generated-doc/out/server/observability.md b/generated-doc/out/server/observability.md index 716362c3cc..0e4e22338c 100644 --- a/generated-doc/out/server/observability.md +++ b/generated-doc/out/server/observability.md @@ -49,7 +49,7 @@ val labels = MetricLabels( Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-prometheus-metrics" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-prometheus-metrics" % "1.2.7" ``` `PrometheusMetrics` encapsulates `CollectorReqistry` and `Metric` instances. It provides several ready to use metrics as @@ -130,7 +130,7 @@ val prometheusMetrics = PrometheusMetrics[Future]("tapir", CollectorRegistry.def Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-opentelemetry-metrics" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-opentelemetry-metrics" % "1.2.7" ``` OpenTelemetry metrics are vendor-agnostic and can be exported using one @@ -157,7 +157,7 @@ val metricsInterceptor = metrics.metricsInterceptor() // add to your server opti Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-datadog-metrics" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-datadog-metrics" % "1.2.7" ``` Datadog metrics are sent as Datadog custom metrics through @@ -224,7 +224,7 @@ val datadogMetrics = DatadogMetrics.default[Future](statsdClient) Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio-metrics" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-zio-metrics" % "1.2.7" ``` Metrics have been integrated into ZIO core in ZIO2. diff --git a/generated-doc/out/server/play.md b/generated-doc/out/server/play.md index 9b1dae7423..542eb13f6e 100644 --- a/generated-doc/out/server/play.md +++ b/generated-doc/out/server/play.md @@ -3,19 +3,19 @@ To expose endpoint as a [play-server](https://www.playframework.com/) first add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-play-server" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-play-server" % "1.2.7" ``` and (if you don't already depend on Play) ```scala -"com.typesafe.play" %% "play-akka-http-server" % "2.8.18" +"com.typesafe.play" %% "play-akka-http-server" % "2.8.19" ``` or ```scala -"com.typesafe.play" %% "play-netty-server" % "2.8.18" +"com.typesafe.play" %% "play-netty-server" % "2.8.19" ``` depending on whether you want to use netty or akka based http-server under the hood. diff --git a/generated-doc/out/server/vertx.md b/generated-doc/out/server/vertx.md index 89fcbd878c..e4d5ee44da 100644 --- a/generated-doc/out/server/vertx.md +++ b/generated-doc/out/server/vertx.md @@ -8,7 +8,7 @@ Vert.x interpreter can be used with different effect systems (cats-effect, ZIO) Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server" % "1.2.7" ``` to use this interpreter with `Future`. @@ -63,7 +63,7 @@ It's also possible to define an endpoint together with the server logic in a sin Add the following dependency ```scala -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-cats" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-cats" % "1.2.7" ``` to use this interpreter with Cats Effect typeclasses. @@ -146,9 +146,9 @@ Add the following dependency ```scala // for zio2: -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio" % "1.2.7" // for zio1: -"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio1" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-vertx-server-zio1" % "1.2.7" ``` to use this interpreter with ZIO. diff --git a/generated-doc/out/server/zio-http4s.md b/generated-doc/out/server/zio-http4s.md index 6d14a0ebdd..35b1f319f5 100644 --- a/generated-doc/out/server/zio-http4s.md +++ b/generated-doc/out/server/zio-http4s.md @@ -9,16 +9,16 @@ The `*-zio` modules depend on ZIO 2.x. For ZIO 1.x support, use modules with the You'll need the following dependency for the `ZServerEndpoint` type alias and helper classes: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.7" ``` or just add the zio-http4s integration which already depends on `tapir-zio`: ```scala // for zio 2: -"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio" % "1.2.7" // for zio 1: -"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio1" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio1" % "1.2.7" ``` Next, instead of the usual `import sttp.tapir._`, you should import (or extend the `ZTapir` trait, see [MyTapir](../mytapir.md)): diff --git a/generated-doc/out/server/ziohttp.md b/generated-doc/out/server/ziohttp.md index f8d705ac55..602cf8f224 100644 --- a/generated-doc/out/server/ziohttp.md +++ b/generated-doc/out/server/ziohttp.md @@ -9,13 +9,13 @@ The `*-zio` modules depend on ZIO 2.x. For ZIO 1.x support, use modules with the You'll need the following dependency for the `ZServerEndpoint` type alias and helper classes: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-zio" % "1.2.7" ``` or just add the zio-http integration which already depends on `tapir-zio`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio-http-server" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-zio-http-server" % "1.2.7" ``` Next, instead of the usual `import sttp.tapir._`, you should import (or extend the `ZTapir` trait, see [MyTapir](../mytapir.md)): @@ -46,7 +46,7 @@ example: import sttp.tapir.PublicEndpoint import sttp.tapir.ztapir._ import sttp.tapir.server.ziohttp.ZioHttpInterpreter -import zio.http.{Http, Request, Response} +import zio.http.{App, Request, Response} import zio._ def countCharacters(s: String): ZIO[Any, Nothing, Int] = @@ -55,8 +55,8 @@ def countCharacters(s: String): ZIO[Any, Nothing, Int] = val countCharactersEndpoint: PublicEndpoint[String, Unit, Int, Any] = endpoint.in(stringBody).out(plainBody[Int]) -val countCharactersHttp: Http[Any, Throwable, Request, Response] = - ZioHttpInterpreter().toHttp(countCharactersEndpoint.zServerLogic(countCharacters)) +val countCharactersHttp: App[Any] = + ZioHttpInterpreter().toApp(countCharactersEndpoint.zServerLogic(countCharacters)) ``` ## Server logic diff --git a/generated-doc/out/testing.md b/generated-doc/out/testing.md index ad5581b670..dc8e337306 100644 --- a/generated-doc/out/testing.md +++ b/generated-doc/out/testing.md @@ -23,7 +23,7 @@ Tapir builds upon the `SttpBackendStub` to enable stubbing using `Endpoint`s or dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.7" ``` Let's assume you are using the [akka http](server/akkahttp.md) interpreter. Given the following server endpoint: @@ -142,7 +142,7 @@ requests matching an endpoint, you can use the tapir `SttpBackendStub` extension Similarly as when testing server interpreters, add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.2.7" ``` And the following imports: @@ -197,7 +197,7 @@ with [mock-server](https://www.mock-server.com/) Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-mock-server" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-mock-server" % "1.2.7" ``` Imports: @@ -268,7 +268,7 @@ result == out To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-testing" % "1.2.6" +"com.softwaremill.sttp.tapir" %% "tapir-testing" % "1.2.7" ``` ### Shadowed endpoints @@ -294,7 +294,7 @@ Results in: ```scala res.toString -// res2: String = "Set(GET /x/y/x, is shadowed by: GET /x/*, GET /x, is shadowed by: GET /x/*)" +// res2: String = "Set(GET /x, is shadowed by: GET /x/*, GET /x/y/x, is shadowed by: GET /x/*)" ``` Example 2: From 67a30cb2fc4f64520e10cb0170636914fd501d7c Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sun, 29 Jan 2023 00:06:03 +0000 Subject: [PATCH 116/120] Update sbt-scala-native to 0.4.10 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 41b8290a2e..3a320c6c09 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -13,7 +13,7 @@ addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.0") addSbtPlugin("io.gatling" % "gatling-sbt" % "4.2.4") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.9") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.10") addSbtPlugin("com.lightbend.akka.grpc" % "sbt-akka-grpc" % "2.1.4") addDependencyTreePlugin From db72cc78336a8a9de6208adb32c9879754ce708b Mon Sep 17 00:00:00 2001 From: scala-steward Date: Tue, 31 Jan 2023 00:05:42 +0000 Subject: [PATCH 117/120] Update scala3-library, ... to 3.2.2 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 262a1182d6..112a1fd957 100644 --- a/build.sbt +++ b/build.sbt @@ -16,7 +16,7 @@ import scala.sys.process.Process val scala2_12 = "2.12.17" val scala2_13 = "2.13.10" -val scala3 = "3.2.1" +val scala3 = "3.2.2" val scala2Versions = List(scala2_12, scala2_13) val scala2_13and3Versions = List(scala2_13, scala3) From 8914aa9be886557f9c16bce54eb85d0934f2c119 Mon Sep 17 00:00:00 2001 From: wwziatka Date: Thu, 2 Feb 2023 11:28:52 +0100 Subject: [PATCH 118/120] Fix invalid sttp-mock-server dependency --- doc/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/testing.md b/doc/testing.md index d5e83d5921..3e06d29284 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -197,7 +197,7 @@ with [mock-server](https://www.mock-server.com/) Add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-mock-server" % "@VERSION@" +"com.softwaremill.sttp.tapir" %% "sttp-mock-server" % "@VERSION@" ``` Imports: From 6a86d4ab7263b2383c219fc3134c0a16093295df Mon Sep 17 00:00:00 2001 From: adamw Date: Fri, 3 Feb 2023 16:30:41 +0100 Subject: [PATCH 119/120] Docs for debugging schema derivation. --- doc/endpoint/schemas.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/endpoint/schemas.md b/doc/endpoint/schemas.md index 51e9470f9b..90b33d7511 100644 --- a/doc/endpoint/schemas.md +++ b/doc/endpoint/schemas.md @@ -64,13 +64,15 @@ implicit lazy val sParent: Schema[Parent] = Schema.derived Note that while schemas for regular types can be safely defined as `val`s, in case of recursive values, the schema values must be `lazy val`s. -```eval_rst -.. note:: +## Debugging schema derivation - When deriving schemas using ``Schema.derived``, the diagnostic information as to schemas for which types is - much richer. Hence, this method may be used when debugging the derivation of a schema which gives compilation errors, - even when otherwise auto derivation is used. -``` +When deriving schemas using `Schema.derived[T]`, in case derivation fails, you'll get information for which part of `T` +the schema cannot be found (e.g. a specific field, or a trait subtype). Given this diagnostic information you can drill +down, and try to derive the schema (again using `Schema.derived`) for the problematic part. Eventually, you'll find the +lowest-level type for which the schema cannot be derived. You might need to provide it manually, or use some kind of +integration layer. + +This method may be used both with automatic and semi-automatic derivation. ## Derivation for recursive types in Scala3 From 837c99472fe68151f5e9dcef353f5b3eb50b5b2c Mon Sep 17 00:00:00 2001 From: adamw Date: Fri, 3 Feb 2023 16:45:32 +0100 Subject: [PATCH 120/120] Docs for debugging schema derivation. --- core/src/main/scala/sttp/tapir/Schema.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/sttp/tapir/Schema.scala b/core/src/main/scala/sttp/tapir/Schema.scala index cbb05ba81f..a46e573cac 100644 --- a/core/src/main/scala/sttp/tapir/Schema.scala +++ b/core/src/main/scala/sttp/tapir/Schema.scala @@ -21,9 +21,10 @@ import scala.annotation.{StaticAnnotation, implicitNotFound} */ @implicitNotFound( msg = """Could not find Schema for type ${T}. -Automatic derivation requires the following import: `import sttp.tapir.generic.auto._` -You can find more details in the docs: https://tapir.softwaremill.com/en/latest/endpoint/schemas.html#schema-derivation -When using datatypes integration remember to import respective schemas/codecs as described in https://tapir.softwaremill.com/en/latest/endpoint/integrations.html""" +Schemas can be derived automatically by adding: `import sttp.tapir.generic.auto._`, or manually using `Schema.derived[T]`. +The latter is also useful for debugging derivation errors. +You can find more details in the docs: https://tapir.softwaremill.com/en/latest/endpoint/schemas.html +When integrating with a third-party datatype library remember to import respective schemas/codecs as described in https://tapir.softwaremill.com/en/latest/endpoint/integrations.html""" ) case class Schema[T]( schemaType: SchemaType[T],