diff --git a/build.sbt b/build.sbt index 1f3796b101..d63924baf1 100644 --- a/build.sbt +++ b/build.sbt @@ -22,7 +22,7 @@ val scala2Versions = List(scala2_12, scala2_13) val scala2And3Versions = scala2Versions ++ List(scala3) val scala2_13And3Versions = List(scala2_13, scala3) val codegenScalaVersions = List(scala2_12) -val examplesScalaVersions = List(scala2_13) +val examplesScalaVersions = List(scala3) val documentationScalaVersion = scala2_13 lazy val clientTestServerPort = settingKey[Int]("Port to run the client interpreter test server on") @@ -238,8 +238,8 @@ lazy val rawAllAggregates = core.projectRefs ++ play29Client.projectRefs ++ tests.projectRefs ++ perfTests.projectRefs ++ + examples2.projectRefs ++ examples.projectRefs ++ - examples3.projectRefs ++ documentation.projectRefs ++ openapiCodegenCore.projectRefs ++ openapiCodegenSbt.projectRefs ++ @@ -542,7 +542,7 @@ lazy val perfTests: ProjectMatrix = (projectMatrix in file("perf-tests")) .settings(http4sTapir := { (genPerfTestTask("http4s.Tapir", "OneRoute")).value }) .settings(http4sVanillaMulti := { (genPerfTestTask("http4s.VanillaMulti", "MultiRoute")).value }) .settings(http4sTapirMulti := { (genPerfTestTask("http4s.TapirMulti", "MultiRoute")).value }) - .jvmPlatform(scalaVersions = examplesScalaVersions) + .jvmPlatform(scalaVersions = List(scala2_13)) .dependsOn(core, akkaHttpServer, http4sServer) // integrations @@ -2015,10 +2015,10 @@ lazy val openapiCodegenCli: ProjectMatrix = (projectMatrix in file("openapi-code // other -lazy val examples: ProjectMatrix = (projectMatrix in file("examples")) +lazy val examples2: ProjectMatrix = (projectMatrix in file("examples2")) .settings(commonJvmSettings) .settings( - name := "tapir-examples", + name := "tapir-examples2", libraryDependencies ++= Seq( "dev.zio" %% "zio-interop-cats" % Versions.zioInteropCats, "org.typelevel" %% "cats-effect" % Versions.catsEffect, @@ -2043,7 +2043,7 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples")) publishArtifact := false, Compile / run / fork := true ) - .jvmPlatform(scalaVersions = examplesScalaVersions) + .jvmPlatform(scalaVersions = List(scala2_13)) .dependsOn( akkaHttpServer, pekkoHttpServer, @@ -2077,25 +2077,54 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples")) protobuf ) -lazy val examples3: ProjectMatrix = (projectMatrix in file("examples3")) +lazy val examples: ProjectMatrix = (projectMatrix in file("examples")) .settings(commonJvmSettings) .settings( - name := "tapir-examples3", + name := "tapir-examples", libraryDependencies ++= Seq( + "com.softwaremill.sttp.apispec" %% "asyncapi-circe-yaml" % Versions.sttpApispec, + "com.softwaremill.sttp.client3" %% "core" % Versions.sttp, + "com.softwaremill.sttp.client3" %% "pekko-http-backend" % Versions.sttp, + "com.softwaremill.sttp.client3" %% "async-http-client-backend-fs2" % Versions.sttp, + "com.softwaremill.sttp.client3" %% "async-http-client-backend-zio" % Versions.sttp, + "com.softwaremill.sttp.client3" %% "async-http-client-backend-cats" % Versions.sttp, + "com.github.jwt-scala" %% "jwt-circe" % Versions.jwtScala, + "org.http4s" %% "http4s-dsl" % Versions.http4s, + "org.http4s" %% "http4s-circe" % Versions.http4s, "org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer, - "com.softwaremill.sttp.client3" %% "core" % Versions.sttp + "io.opentelemetry" % "opentelemetry-sdk" % Versions.openTelemetry, + "io.opentelemetry" % "opentelemetry-sdk-metrics" % Versions.openTelemetry, + "io.opentelemetry" % "opentelemetry-exporter-otlp" % Versions.openTelemetry, + scalaTest.value ), libraryDependencies ++= loggerDependencies, publishArtifact := false ) - .jvmPlatform(scalaVersions = List(scala3)) + .jvmPlatform(scalaVersions = examplesScalaVersions) .dependsOn( + datadogMetrics, + prometheusMetrics, + opentelemetryMetrics, + zioMetrics, circeJson, http4sServer, + pekkoHttpServer, + armeriaServer, nettyServer, + jdkhttpServer, + nettyServerCats, + http4sClient, picklerJson, sttpClient, - swaggerUiBundle + swaggerUiBundle, + http4sServerZio, + nettyServerZio, + zioHttpServer, + zioJson, + redocBundle, + sttpStubServer, + asyncapiDocs, + iron ) //TODO this should be invoked by compilation process, see #https://github.com/scalameta/mdoc/issues/355 diff --git a/doc/endpoint/integrations.md b/doc/endpoint/integrations.md index 8b9cc1d0d1..e0470368ce 100644 --- a/doc/endpoint/integrations.md +++ b/doc/endpoint/integrations.md @@ -66,6 +66,80 @@ The iron codecs contain a validator which apply the constraint to validated valu Similarly to `tapir-refined`, you can find the predicate logic in `integrations/iron/src/main/scala/sttp/iron/codec/iron/TapirCodecIron.scala` and provide your own given `ValidatorForPredicate[T, P]` in scope using `ValidatorForPredicate.fromPrimitiveValidator` +### Validation + +When using `iron` in the server e.g. in case classes that JSON request body +is parsed to, some additional steps need to be taken to properly +report `iron` validation errors. + +[Iron](https://github.com/Iltotore/iron) is operating on type level while regular tapir +validation works on case classes created from parsed JSON. When `iron` types are used +in a case class, and passed values are invalid for `iron` types, creation is impossible because `iron` +does not allow creating guarded type instance. +Because it is not possible to create case class for `ServerInterpreter` it looks like JSON parsing error not +like validation error. In such case no error message is displayed to user. + +To properly report `iron` errors it is necessary to recognize them in failure intereptor. +Custom JSON parsing is necessary anyway so custom exception can be thrown in case of `iron` +refinement error and then matched in failure interceptor. + +Example for `circe`: + +```scala +case class IronException(error: String) extends Exception(error) + +inline given (using inline constraint: Constraint[Int, Positive]): Decoder[Age] = summon[Decoder[Int]].map(unrefinedValue => + unrefinedValue.refineEither[Positive] match + case Right(value) => value + case Left(errorMessage) => throw IronException(s"Could not refine value $unrefinedValue: $errorMessage") +) +``` + +Then failure handler matching `IronException` is needed. Remember to create the interceptor: + +```scala +private def failureDetailMessage(failure: DecodeResult.Failure): Option[String] = failure match { + case Error(_, JsonDecodeException(_, IronException(errorMessage))) => Some(errorMessage) + case Error(_, IronException(errorMessage)) => Some(errorMessage) + case other => FailureMessages.failureDetailMessage(other) +} + +private def failureMessage(ctx: DecodeFailureContext): String = { + val base = FailureMessages.failureSourceMessage(ctx.failingInput) + val detail = failureDetailMessage(ctx.failure) + FailureMessages.combineSourceAndDetail(base, detail) +} + +def ironFailureHandler[T[_]] = new DefaultDecodeFailureHandler[T]( + DefaultDecodeFailureHandler.respond, + failureMessage, + DefaultDecodeFailureHandler.failureResponse +) + +def ironDecodeFailureInterceptor[T[_]] = new DecodeFailureInterceptor[T](ironFailureHandler[T]) +``` + +...and add it to server options: + +```scala +override def run = NettyCatsServer + .io() + .use { server => + // Don't forget to add the interceptor to server options + val optionsWithInterceptor = server.options.prependInterceptor(ironDecodeFailureInterceptor) + for { + binding <- server + .port(port) + .host(host) + .options(optionsWithInterceptor) + .addEndpoint(endpoint) + .start() + //... + } + } +``` + + ## Enumeratum integration The `tapir-enumeratum` module provides schemas, validators and codecs for [Enumeratum](https://github.com/lloydmeta/enumeratum) diff --git a/doc/examples.md b/doc/examples.md index d0685363d9..06cc9f1c15 100644 --- a/doc/examples.md +++ b/doc/examples.md @@ -1,6 +1,6 @@ # Examples -The [examples](https://github.com/softwaremill/tapir/tree/master/examples/src/main/scala/sttp/tapir/examples) and [examples3](https://github.com/softwaremill/tapir/tree/master/examples3/src/main/scala/sttp/tapir/examples3) sub-projects (the latter containing Scala 3-only code) contains a number of runnable tapir usage examples, using various interpreters and showcasing different features. +The [examples](https://github.com/softwaremill/tapir/tree/master/examples/src/main/scala/sttp/tapir/examples) and [examples2](https://github.com/softwaremill/tapir/tree/master/examples2/src/main/scala/sttp/tapir/examples2) sub-projects (the latter containing Scala 2-only code) contains a number of runnable tapir usage examples, using various interpreters and showcasing different features. ## Generate a tapir project diff --git a/examples/src/main/scala/sttp/tapir/examples/BooksExample.scala b/examples/src/main/scala/sttp/tapir/examples/BooksExample.scala index 50107eb61e..f1b1bd2fc2 100644 --- a/examples/src/main/scala/sttp/tapir/examples/BooksExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/BooksExample.scala @@ -1,7 +1,7 @@ package sttp.tapir.examples import com.typesafe.scalalogging.StrictLogging -import sttp.tapir.generic.auto._ +import sttp.tapir.generic.auto.* object BooksExample extends App with StrictLogging { type Limit = Option[Int] @@ -16,9 +16,9 @@ object BooksExample extends App with StrictLogging { /** Descriptions of endpoints used in the example. */ object Endpoints { - import io.circe.generic.auto._ - import sttp.tapir._ - import sttp.tapir.json.circe._ + import io.circe.generic.auto.* + import sttp.tapir.* + import sttp.tapir.json.circe.* // All endpoints report errors as strings, and have the common path prefix '/books' private val baseEndpoint = endpoint.errorOut(stringBody).in("books") @@ -83,7 +83,7 @@ object BooksExample extends App with StrictLogging { // - import Endpoints._ + import Endpoints.* import sttp.tapir.server.ServerEndpoint import scala.concurrent.Future @@ -112,7 +112,7 @@ object BooksExample extends App with StrictLogging { Right[String, Vector[Book]](Library.getBooks(query)) } - // interpreting the endpoint description and converting it to an akka-http route, providing the logic which + // interpreting the endpoint description and converting it to an pekko-http route, providing the logic which // should be run when the endpoint is invoked. List( addBook.serverLogic((bookAddLogic _).tupled), @@ -125,29 +125,29 @@ object BooksExample extends App with StrictLogging { import sttp.tapir.swagger.bundle.SwaggerInterpreter // interpreting the endpoint descriptions as yaml openapi documentation - // exposing the docs using SwaggerUI endpoints, interpreted as an akka-http route + // exposing the docs using SwaggerUI endpoints, interpreted as an pekko-http route SwaggerInterpreter().fromEndpoints(List(addBook, booksListing, booksListingByGenre), "The Tapir Library", "1.0") } def startServer(serverEndpoints: List[ServerEndpoint[Any, Future]]): Unit = { - import akka.actor.ActorSystem - import akka.http.scaladsl.Http + import org.apache.pekko.actor.ActorSystem + import org.apache.pekko.http.scaladsl.Http import scala.concurrent.Await - import scala.concurrent.duration._ + import scala.concurrent.duration.* - import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter + import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter implicit val actorSystem: ActorSystem = ActorSystem() import actorSystem.dispatcher - val routes = AkkaHttpServerInterpreter().toRoute(serverEndpoints) + val routes = PekkoHttpServerInterpreter().toRoute(serverEndpoints) Await.result(Http().newServerAt("localhost", 8080).bindFlow(routes), 1.minute) logger.info("Server started") } def makeClientRequest(): Unit = { - import sttp.client3._ + import sttp.client3.* import sttp.tapir.client.sttp.SttpClientInterpreter val client = SttpClientInterpreter().toQuickClient(booksListing, Some(uri"http://localhost:8080")) diff --git a/examples3/src/main/scala/sttp/tapir/examples3/BooksPicklerExample.scala b/examples/src/main/scala/sttp/tapir/examples/BooksPicklerExample.scala similarity index 99% rename from examples3/src/main/scala/sttp/tapir/examples3/BooksPicklerExample.scala rename to examples/src/main/scala/sttp/tapir/examples/BooksPicklerExample.scala index f8585039f0..3e83d71cf8 100644 --- a/examples3/src/main/scala/sttp/tapir/examples3/BooksPicklerExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/BooksPicklerExample.scala @@ -1,4 +1,4 @@ -package sttp.tapir.examples3 +package sttp.tapir.examples import com.typesafe.scalalogging.StrictLogging import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding} diff --git a/examples3/src/main/scala/sttp/tapir/examples3/ErrorUnionTypesHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/ErrorUnionTypesHttp4sServer.scala similarity index 99% rename from examples3/src/main/scala/sttp/tapir/examples3/ErrorUnionTypesHttp4sServer.scala rename to examples/src/main/scala/sttp/tapir/examples/ErrorUnionTypesHttp4sServer.scala index 92dca34652..a8224d241d 100644 --- a/examples3/src/main/scala/sttp/tapir/examples3/ErrorUnionTypesHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ErrorUnionTypesHttp4sServer.scala @@ -1,4 +1,4 @@ -package sttp.tapir.examples3 +package sttp.tapir.examples import cats.effect.* import cats.syntax.all.* diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldArmeriaServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldArmeriaServer.scala index cffe16e43e..514a73644b 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldArmeriaServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldArmeriaServer.scala @@ -1,11 +1,12 @@ package sttp.tapir.examples import com.linecorp.armeria.server.Server -import scala.concurrent.Future -import sttp.client3.{HttpURLConnectionBackend, Identity, SttpBackend, UriContext, asStringAlways, basicRequest} import sttp.capabilities.armeria.ArmeriaStreams +import sttp.client3.{HttpURLConnectionBackend, Identity, SttpBackend, UriContext, asStringAlways, basicRequest} import sttp.tapir.server.armeria.{ArmeriaFutureServerInterpreter, TapirService} -import sttp.tapir.{PublicEndpoint, endpoint, query, stringBody} +import sttp.tapir.* + +import scala.concurrent.Future object HelloWorldArmeriaServer extends App { diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala index 75efd5458b..c957690050 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala @@ -1,12 +1,12 @@ package sttp.tapir.examples -import cats.effect._ -import cats.syntax.all._ +import cats.effect.* +import cats.syntax.all.* import org.http4s.HttpRoutes -import org.http4s.server.Router import org.http4s.blaze.server.BlazeServerBuilder -import sttp.client3._ -import sttp.tapir._ +import org.http4s.server.Router +import sttp.client3.* +import sttp.tapir.* import sttp.tapir.server.http4s.Http4sServerInterpreter import scala.concurrent.ExecutionContext diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldJdkHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldJdkHttpServer.scala index 3fddae3caf..8cce9d7965 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldJdkHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldJdkHttpServer.scala @@ -2,8 +2,8 @@ package sttp.tapir.examples import sttp.client3.{HttpURLConnectionBackend, Identity, Response, SttpBackend, UriContext, asStringAlways, basicRequest} import sttp.model.StatusCode -import sttp.tapir.server.jdkhttp._ -import sttp.tapir.{PublicEndpoint, endpoint, query, stringBody} +import sttp.tapir.server.jdkhttp.* +import sttp.tapir.* object HelloWorldJdkHttpServer extends App { diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala index 9ab3d19be5..efc9c955e7 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala @@ -1,11 +1,10 @@ package sttp.tapir.examples -import cats.effect.IO -import cats.effect.IOApp +import cats.effect.{IO, IOApp} import sttp.client3.{HttpURLConnectionBackend, Identity, SttpBackend, UriContext, asStringAlways, basicRequest} import sttp.model.StatusCode -import sttp.tapir.{PublicEndpoint, endpoint, query, stringBody} import sttp.tapir.server.netty.cats.NettyCatsServer +import sttp.tapir.* object HelloWorldNettyCatsServer extends IOApp.Simple { // One endpoint on GET /hello with query parameter `name` diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyFutureServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyFutureServer.scala index 7d7a3ddabc..05d5b3a799 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyFutureServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyFutureServer.scala @@ -3,7 +3,7 @@ package sttp.tapir.examples import sttp.client3.{HttpURLConnectionBackend, Identity, SttpBackend, UriContext, asStringAlways, basicRequest} import sttp.model.StatusCode import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding} -import sttp.tapir.{PublicEndpoint, endpoint, query, stringBody} +import sttp.tapir.* import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration.Duration diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldPekkoServer.scala index 49856a5625..ab22daf4d3 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldPekkoServer.scala @@ -3,12 +3,12 @@ package sttp.tapir.examples import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Route -import sttp.client3._ -import sttp.tapir._ +import sttp.client3.* +import sttp.tapir.* import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter +import scala.concurrent.duration.* import scala.concurrent.{Await, Future} -import scala.concurrent.duration._ object HelloWorldPekkoServer extends App { implicit val actorSystem: ActorSystem = ActorSystem() @@ -24,14 +24,16 @@ object HelloWorldPekkoServer extends App { PekkoHttpServerInterpreter().toRoute(helloWorld.serverLogicSuccess(name => Future.successful(s"Hello, $name!"))) // starting the server - val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(helloWorldRoute).map { _ => + val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(helloWorldRoute).map { binding => // testing val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() val result: String = basicRequest.response(asStringAlways).get(uri"http://localhost:8080/hello?name=Frodo").send(backend).body println("Got result: " + result) assert(result == "Hello, Frodo!") + + binding } - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) + Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) } diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala index fe4e75fce8..f5f2c7c20d 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala @@ -1,13 +1,12 @@ package sttp.tapir.examples import sttp.tapir.PublicEndpoint -import sttp.tapir.ztapir._ -import sttp.tapir.generic.auto._ -import sttp.tapir.json.zio._ +import sttp.tapir.generic.auto.* +import sttp.tapir.json.zio.* import sttp.tapir.server.ziohttp.ZioHttpInterpreter -import zio.http.HttpApp -import zio.http.Server -import zio._ +import sttp.tapir.ztapir.* +import zio.* +import zio.http.{HttpApp, Server} import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} object HelloWorldZioHttpServer extends ZIOAppDefault { diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala index 93ac205adb..74df29d364 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala @@ -1,17 +1,17 @@ package sttp.tapir.examples -import cats.syntax.all._ -import io.circe.generic.auto._ -import org.http4s._ +import cats.syntax.all.* +import io.circe.generic.auto.* +import org.http4s.* import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.server.Router import sttp.tapir.PublicEndpoint -import sttp.tapir.generic.auto._ -import sttp.tapir.json.circe._ +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter import sttp.tapir.swagger.bundle.SwaggerInterpreter -import sttp.tapir.ztapir._ -import zio.interop.catz._ +import sttp.tapir.ztapir.* +import zio.interop.catz.* import zio.{Console, IO, Layer, RIO, ZIO, ZIOAppDefault, ZLayer} object ZioEnvExampleHttp4sServer extends ZIOAppDefault { diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala index c472d93d9f..05e157ae58 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala @@ -1,17 +1,17 @@ package sttp.tapir.examples -import cats.syntax.all._ -import io.circe.generic.auto._ -import org.http4s._ +import cats.syntax.all.* +import io.circe.generic.auto.* +import org.http4s.* import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.server.Router import sttp.tapir.PublicEndpoint -import sttp.tapir.generic.auto._ -import sttp.tapir.json.circe._ +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter import sttp.tapir.swagger.bundle.SwaggerInterpreter -import sttp.tapir.ztapir._ -import zio.interop.catz._ +import sttp.tapir.ztapir.* +import zio.interop.catz.* import zio.{ExitCode, Task, URIO, ZIO, ZIOAppDefault} object ZioExampleHttp4sServer extends ZIOAppDefault { diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala index ce4309f90f..942a30e660 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala @@ -1,14 +1,13 @@ package sttp.tapir.examples -import io.circe.generic.auto._ +import io.circe.generic.auto.* import sttp.tapir.PublicEndpoint -import sttp.tapir.generic.auto._ -import sttp.tapir.json.circe._ +import sttp.tapir.generic.auto.* +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.Server +import sttp.tapir.ztapir.* +import zio.http.{HttpApp, Server} import zio.{ExitCode, Task, URIO, ZIO, ZIOAppDefault, ZLayer} object ZioExampleZioHttpServer extends ZIOAppDefault { diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala b/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala index 28d2f5dee0..1260aeb15e 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala @@ -1,16 +1,16 @@ package sttp.tapir.examples -import org.http4s._ +import org.http4s.* import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.server.Router -import sttp.client3._ +import sttp.client3.* import sttp.client3.asynchttpclient.zio.AsyncHttpClientZioBackend -import sttp.tapir.examples.UserAuthenticationLayer._ -import sttp.tapir.server.http4s.ztapir._ -import sttp.tapir.ztapir._ +import sttp.tapir.examples.UserAuthenticationLayer.* +import sttp.tapir.server.http4s.ztapir.* +import sttp.tapir.ztapir.* import zio.Console.printLine -import zio._ -import zio.interop.catz._ +import zio.* +import zio.interop.catz.* object ZioPartialServerLogicHttp4s extends ZIOAppDefault { diff --git a/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala b/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala index 25ce30abc8..2f4393b712 100644 --- a/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala @@ -2,11 +2,11 @@ package sttp.tapir.examples.client import cats.effect.{ExitCode, IO, IOApp} import com.typesafe.scalalogging.StrictLogging -import io.circe.generic.auto._ -import sttp.tapir._ +import io.circe.generic.auto.* +import sttp.tapir.* import sttp.tapir.client.http4s.Http4sClientInterpreter -import sttp.tapir.generic.auto._ -import sttp.tapir.json.circe._ +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* object Http4sClientExample extends IOApp with StrictLogging { @@ -20,12 +20,12 @@ object Http4sClientExample extends IOApp with StrictLogging { // Define http4s routes that will be used to test the request. private val http4sRoutes = { - import io.circe.generic.auto._ - import io.circe.syntax._ - import org.http4s._ - import org.http4s.circe.CirceEntityEncoder._ - import org.http4s.dsl.io._ - import org.http4s.implicits._ + import io.circe.generic.auto.* + import io.circe.syntax.* + import org.http4s.* + import org.http4s.circe.CirceEntityEncoder.* + import org.http4s.dsl.io.* + import org.http4s.implicits.* HttpRoutes .of[IO] { case GET -> Root / "users" / IntVar(userId) => diff --git a/examples/src/main/scala/sttp/tapir/examples/custom_types/BooksExampleSemiauto.scala b/examples/src/main/scala/sttp/tapir/examples/custom_types/BooksExampleSemiauto.scala index 0ad68d3414..baed59ee22 100644 --- a/examples/src/main/scala/sttp/tapir/examples/custom_types/BooksExampleSemiauto.scala +++ b/examples/src/main/scala/sttp/tapir/examples/custom_types/BooksExampleSemiauto.scala @@ -22,9 +22,9 @@ object BooksExampleSemiauto extends App with StrictLogging { /** Descriptions of endpoints used in the example. */ object Endpoints { - import io.circe.generic.auto._ - import sttp.tapir._ - import sttp.tapir.json.circe._ + import io.circe.generic.auto.* + import sttp.tapir.* + import sttp.tapir.json.circe.* // All endpoints report errors as strings, and have the common path prefix '/books' private val baseEndpoint = endpoint.errorOut(stringBody).in("books") @@ -89,7 +89,7 @@ object BooksExampleSemiauto extends App with StrictLogging { // - import Endpoints._ + import Endpoints.* import sttp.tapir.server.ServerEndpoint import scala.concurrent.Future @@ -137,23 +137,23 @@ object BooksExampleSemiauto extends App with StrictLogging { } def startServer(serverEndpoints: List[ServerEndpoint[Any, Future]]): Unit = { - import akka.actor.ActorSystem - import akka.http.scaladsl.Http - import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter + import org.apache.pekko.actor.ActorSystem + import org.apache.pekko.http.scaladsl.Http + import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter import scala.concurrent.Await - import scala.concurrent.duration._ + import scala.concurrent.duration.* implicit val actorSystem: ActorSystem = ActorSystem() import actorSystem.dispatcher - val routes = AkkaHttpServerInterpreter().toRoute(serverEndpoints) + val routes = PekkoHttpServerInterpreter().toRoute(serverEndpoints) Await.result(Http().newServerAt("localhost", 8080).bindFlow(routes), 1.minute) logger.info("Server started") } def makeClientRequest(): Unit = { - import sttp.client3._ + import sttp.client3.* import sttp.tapir.client.sttp.SttpClientInterpreter val client = SttpClientInterpreter().toQuickClient(booksListing, Some(uri"http://localhost:8080")) diff --git a/examples/src/main/scala/sttp/tapir/examples/custom_types/CommaSeparatedQueryParameter.scala b/examples/src/main/scala/sttp/tapir/examples/custom_types/CommaSeparatedQueryParameter.scala index 07b2baf1ef..904e37eaa3 100644 --- a/examples/src/main/scala/sttp/tapir/examples/custom_types/CommaSeparatedQueryParameter.scala +++ b/examples/src/main/scala/sttp/tapir/examples/custom_types/CommaSeparatedQueryParameter.scala @@ -1,10 +1,10 @@ package sttp.tapir.examples.custom_types -import sttp.tapir._ +import sttp.tapir.* +import sttp.tapir.model.{CommaSeparated, Delimited} import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding} import sttp.tapir.swagger.bundle.SwaggerInterpreter -import sttp.tapir.model.{Delimited, CommaSeparated} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration.Duration diff --git a/examples/src/main/scala/sttp/tapir/examples/custom_types/EndpointWithCustomTypes.scala b/examples/src/main/scala/sttp/tapir/examples/custom_types/EndpointWithCustomTypes.scala index 37c716f37a..c457a9e152 100644 --- a/examples/src/main/scala/sttp/tapir/examples/custom_types/EndpointWithCustomTypes.scala +++ b/examples/src/main/scala/sttp/tapir/examples/custom_types/EndpointWithCustomTypes.scala @@ -1,10 +1,10 @@ package sttp.tapir.examples.custom_types +import io.circe.generic.auto.* import io.circe.{Decoder, Encoder} -import sttp.tapir._ -import sttp.tapir.json.circe._ -import sttp.tapir.generic.auto._ -import io.circe.generic.auto._ +import sttp.tapir.* +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* object EndpointWithCustomTypes { // An over-complicated, example custom type diff --git a/examples/src/main/scala/sttp/tapir/examples/errors/CustomErrorsOnDecodeFailureAkkaServer.scala b/examples/src/main/scala/sttp/tapir/examples/errors/CustomErrorsOnDecodeFailurePekkoServer.scala similarity index 67% rename from examples/src/main/scala/sttp/tapir/examples/errors/CustomErrorsOnDecodeFailureAkkaServer.scala rename to examples/src/main/scala/sttp/tapir/examples/errors/CustomErrorsOnDecodeFailurePekkoServer.scala index 4923596aca..782efd7469 100644 --- a/examples/src/main/scala/sttp/tapir/examples/errors/CustomErrorsOnDecodeFailureAkkaServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/errors/CustomErrorsOnDecodeFailurePekkoServer.scala @@ -1,18 +1,20 @@ package sttp.tapir.examples.errors -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.server.Route -import sttp.tapir._ -import sttp.tapir.server.akkahttp.{AkkaHttpServerInterpreter, AkkaHttpServerOptions} +import org.apache.pekko.actor.ActorSystem +import org.apache.pekko.http.scaladsl.Http +import org.apache.pekko.http.scaladsl.server.Route +import sttp.tapir.* +import sttp.tapir.server.pekkohttp.{PekkoHttpServerInterpreter, PekkoHttpServerOptions} import scala.concurrent.{Await, Future} -import scala.concurrent.duration._ -import sttp.client3._ +import scala.concurrent.duration.* +import sttp.client3.* import sttp.monad.FutureMonad import sttp.tapir.server.interceptor.decodefailure.{DecodeFailureHandler, DefaultDecodeFailureHandler} -object CustomErrorsOnDecodeFailureAkkaServer extends App { +import scala.util.{Failure, Success} + +object CustomErrorsOnDecodeFailurePekkoServer extends App { implicit val actorSystem: ActorSystem = ActorSystem() import actorSystem.dispatcher @@ -23,10 +25,10 @@ object CustomErrorsOnDecodeFailureAkkaServer extends App { // by default, decoding errors will be returned as a 400 response with body e.g. "Invalid value for: query parameter amount" // the defaults are defined in ServerDefaults - // this can be customised by setting the appropriate option in the server options, passed implicitly to toRoute - implicit val customServerOptions: AkkaHttpServerOptions = AkkaHttpServerOptions.customiseInterceptors + // this can be customised by setting the appropriate option in the server options, passed to ServerInterpreter + val customServerOptions: PekkoHttpServerOptions = PekkoHttpServerOptions.customiseInterceptors .decodeFailureHandler( - DecodeFailureHandler(ctx => + DecodeFailureHandler(ctx => { ctx.failingInput match { // when defining how a decode failure should be handled, we need to describe the output to be used, and // a value for this output @@ -35,14 +37,14 @@ object CustomErrorsOnDecodeFailureAkkaServer extends App { // in other cases, using the default behavior case _ => DefaultDecodeFailureHandler[Future](ctx) } - ) + }) ) .options - val amountRoute: Route = AkkaHttpServerInterpreter().toRoute(amountEndpoint.serverLogicSuccess(_ => Future.successful(()))) + val amountRoute: Route = PekkoHttpServerInterpreter(customServerOptions).toRoute(amountEndpoint.serverLogicSuccess(_ => Future.successful(()))) // starting the server - val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(amountRoute).map { _ => + val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(amountRoute).map { binding => // testing val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() @@ -54,8 +56,10 @@ object CustomErrorsOnDecodeFailureAkkaServer extends App { // incorrect request, parameter does not parse, error val result2: Either[String, String] = basicRequest.get(uri"http://localhost:8080/?amount=xyz").send(backend).body println("Got result: " + result2) - assert(result2 == Left("Incorrect format!!!")) + assert(result2 == Right("Incorrect format!!!")) + + binding } - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) + Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) } diff --git a/examples/src/main/scala/sttp/tapir/examples/errors/ErrorOutputsAkkaServer.scala b/examples/src/main/scala/sttp/tapir/examples/errors/ErrorOutputsPekkoServer.scala similarity index 66% rename from examples/src/main/scala/sttp/tapir/examples/errors/ErrorOutputsAkkaServer.scala rename to examples/src/main/scala/sttp/tapir/examples/errors/ErrorOutputsPekkoServer.scala index 070b2a26ef..8d06426278 100644 --- a/examples/src/main/scala/sttp/tapir/examples/errors/ErrorOutputsAkkaServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/errors/ErrorOutputsPekkoServer.scala @@ -1,19 +1,19 @@ package sttp.tapir.examples.errors -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.server.Route -import sttp.client3._ -import sttp.tapir.generic.auto._ -import sttp.tapir._ -import sttp.tapir.server.akkahttp._ -import sttp.tapir.json.circe._ -import io.circe.generic.auto._ - -import scala.concurrent.duration._ +import org.apache.pekko.actor.ActorSystem +import org.apache.pekko.http.scaladsl.Http +import org.apache.pekko.http.scaladsl.server.Route +import sttp.client3.* +import sttp.tapir.generic.auto.* +import sttp.tapir.* +import sttp.tapir.server.pekkohttp.* +import sttp.tapir.json.circe.* +import io.circe.generic.auto.* + +import scala.concurrent.duration.* import scala.concurrent.{Await, Future} -object ErrorOutputsAkkaServer extends App { +object ErrorOutputsPekkoServer extends App { implicit val actorSystem: ActorSystem = ActorSystem() import actorSystem.dispatcher @@ -27,13 +27,13 @@ object ErrorOutputsAkkaServer extends App { .errorOut(stringBody) // converting an endpoint to a route - val errorOrJsonRoute: Route = AkkaHttpServerInterpreter().toRoute(errorOrJson.serverLogic { + val errorOrJsonRoute: Route = PekkoHttpServerInterpreter().toRoute(errorOrJson.serverLogic { case x if x < 0 => Future.successful(Left("Invalid parameter, smaller than 0!")) case x => Future.successful(Right(Result(x * 2))) }) // starting the server - val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(errorOrJsonRoute).map { _ => + val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(errorOrJsonRoute).map { binding => // testing val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() @@ -44,7 +44,9 @@ object ErrorOutputsAkkaServer extends App { val result2: Either[String, String] = basicRequest.get(uri"http://localhost:8080?amount=21").send(backend).body println("Got result (2): " + result2) assert(result2 == Right("""{"result":42}""")) + + binding } - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) + Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) } diff --git a/examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala b/examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala new file mode 100644 index 0000000000..9b0019156b --- /dev/null +++ b/examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala @@ -0,0 +1,125 @@ +package sttp.tapir.examples.errors + +import cats.effect.{IO, IOApp} +import io.github.iltotore.iron.constraint.string.Match +import io.github.iltotore.iron.constraint.numeric.Positive +import io.github.iltotore.iron.:| +import io.github.iltotore.iron.autoRefine +import io.github.iltotore.iron.{Constraint, refineEither} +import io.circe.generic.auto.* +import io.circe.{Decoder, Encoder} +import sttp.client3.* +import sttp.tapir.* +import sttp.tapir.DecodeResult.Error +import sttp.tapir.DecodeResult.Error.JsonDecodeException +import sttp.tapir.server.interceptor.DecodeFailureContext +import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler.FailureMessages +import sttp.tapir.server.interceptor.decodefailure.{DecodeFailureInterceptor, DefaultDecodeFailureHandler} + +import sttp.client3.{HttpURLConnectionBackend, Identity, SttpBackend, UriContext, asStringAlways, basicRequest} +import sttp.model.StatusCode +import sttp.tapir.server.netty.cats.NettyCatsServer +import sttp.tapir.json.circe.* +import sttp.tapir.codec.iron.given +import sttp.tapir.generic.auto.* + +object IronRefinementErrorsNettyServer extends IOApp.Simple { + + case class IronException(error: String) extends Exception(error) + + type Guard = Match["^[A-Z][a-z]+$"] + type Age = Int :| Positive + + case class Person(name: String, age: Age) + + inline given Encoder[Age] = summon[Encoder[Int]].contramap(_.asInstanceOf[Int]) + + // Decoder throwing custom exception when refinement fails + inline given (using inline constraint: Constraint[Int, Positive]): Decoder[Age] = summon[Decoder[Int]].map(unrefinedValue => + unrefinedValue.refineEither[Positive] match + case Right(value) => value + case Left(errorMessage) => throw IronException(s"Could not refine value $unrefinedValue: $errorMessage") + ) + + val addPerson: PublicEndpoint[Person, String, String, Any] = endpoint.post + .in("add") + .in( + jsonBody[Person] + .description("The person to add") + .example(Person("Warski", 30)) + ) + .errorOut(stringBody) + .out(stringBody) + + val addPersonServerEndpoint = addPerson + .serverLogic[IO](person => IO.pure[Either[String, String]](Right(s"It's OK! Got $person"))) + + // Handle failure, when error contains custom exception it means iron refinement failed + // and we can add the failure details to the error message. + private def failureDetailMessage(failure: DecodeResult.Failure): Option[String] = failure match { + case Error(_, JsonDecodeException(_, IronException(errorMessage))) => Some(errorMessage) + case Error(_, IronException(errorMessage)) => Some(errorMessage) + case other => FailureMessages.failureDetailMessage(other) + } + + private def failureMessage(ctx: DecodeFailureContext): String = { + val base = FailureMessages.failureSourceMessage(ctx.failingInput) + val detail = failureDetailMessage(ctx.failure) + FailureMessages.combineSourceAndDetail(base, detail) + } + + def ironFailureHandler[T[_]] = new DefaultDecodeFailureHandler[T]( + DefaultDecodeFailureHandler.respond, + failureMessage, + DefaultDecodeFailureHandler.failureResponse + ) + + // Interceptor + def ironDecodeFailureInterceptor[T[_]] = new DecodeFailureInterceptor[T](ironFailureHandler[T]) + + private val declaredPort = 9090 + private val declaredHost = "localhost" + + override def run = NettyCatsServer + .io() + .use { server => + // Don't forget to add the interceptor to server options + val optionsWithInterceptor = server.options.prependInterceptor(ironDecodeFailureInterceptor) + for { + binding <- server + .port(declaredPort) + .host(declaredHost) + .options(optionsWithInterceptor) + .addEndpoint(addPersonServerEndpoint) + .start() + result <- IO + .blocking { + val port = binding.port + val host = binding.hostName + + val url = uri"http://$host:$port/add" + println(s"Server started at port = $port") + + assert(port == declaredPort, "Ports don't match") + assert(host == declaredHost, "Hosts don't match") + + val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() + + println("Sending valid request") + + val validPersonJson = """{ "name": "Warski", "age": 25 }""" + val body = basicRequest.response(asStringAlways).post(url).body(validPersonJson).send(backend).body + println(s"Response: $body") + + println("Sending invalid request") + val invalidPersonJson = """{ "name": "Warski", "age": -1 }""" + val response = basicRequest.response(asStringAlways).post(url).body(invalidPersonJson).send(backend) + println(s"Response status ${response.code}, body: ${response.body}") + assert(response.code == StatusCode(400)) + // Iron refinement failed - details should be received in response body + assert(response.body == "Invalid value for: body (Could not refine value -1: Should be strictly positive)") + } + .guarantee(binding.stop()) + } yield result + } +} diff --git a/examples/src/main/scala/sttp/tapir/examples/logging/ZioLoggingWithCorrelationIdNettyServer.scala b/examples/src/main/scala/sttp/tapir/examples/logging/ZioLoggingWithCorrelationIdNettyServer.scala index 8aec28f32d..fdfa3a0adc 100644 --- a/examples/src/main/scala/sttp/tapir/examples/logging/ZioLoggingWithCorrelationIdNettyServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/logging/ZioLoggingWithCorrelationIdNettyServer.scala @@ -3,10 +3,10 @@ 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.interceptor.{RequestInterceptor, RequestResult} import sttp.tapir.server.netty.zio.{NettyZioServer, NettyZioServerOptions} -import sttp.tapir.ztapir._ +import sttp.tapir.ztapir.* import zio.{ExitCode, Task, URIO, ZIO, ZIOAppDefault, durationInt} object ZioLoggingWithCorrelationIdNettyServer extends ZIOAppDefault { diff --git a/examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadAkkaServer.scala b/examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadAkkaServer.scala deleted file mode 100644 index 739aa3b603..0000000000 --- a/examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadAkkaServer.scala +++ /dev/null @@ -1,62 +0,0 @@ -package sttp.tapir.examples.multipart - -import java.io.PrintWriter - -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.server.Route -import sttp.client3._ -import sttp.tapir.generic.auto._ -import sttp.model.Part -import sttp.tapir._ -import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter - -import scala.concurrent.{Await, Future} -import scala.concurrent.duration._ - -object MultipartFormUploadAkkaServer extends App { - implicit val actorSystem: ActorSystem = ActorSystem() - import actorSystem.dispatcher - - // the class representing the multipart data - // - // parts can be referenced directly; if part metadata is needed, we define the type wrapped with Part[_]. - // - // note that for binary parts need to be buffered either in-memory or in the filesystem anyway (the whole request - // has to be read to find out what are the parts), so handling multipart requests in a purely streaming fashion is - // not possible - case class UserProfile(name: String, hobby: Option[String], age: Int, photo: Part[TapirFile]) - - // corresponds to: POST /user/profile [multipart form data with fields name, hobby, age, photo] - val setProfile: PublicEndpoint[UserProfile, Unit, String, Any] = - endpoint.post.in("user" / "profile").in(multipartBody[UserProfile]).out(stringBody) - - // converting an endpoint to a route (providing server-side logic); extension method comes from imported packages - val setProfileRoute: Route = AkkaHttpServerInterpreter().toRoute(setProfile.serverLogicSuccess { data => - Future { - val response = s"Received: ${data.name} / ${data.hobby} / ${data.age} / ${data.photo.fileName} (${data.photo.body.length()})" - data.photo.body.delete() - response - } - }) - - // starting the server - val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(setProfileRoute).map { _ => - val testFile = java.io.File.createTempFile("user-123", ".jpg") - val pw = new PrintWriter(testFile); pw.write("This is not a photo"); pw.close() - - // testing - val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() - val result: String = basicRequest - .response(asStringAlways) - .get(uri"http://localhost:8080/user/profile") - .multipartBody(multipart("name", "Frodo"), multipart("hobby", "hiking"), multipart("age", "33"), multipartFile("photo", testFile)) - .send(backend) - .body - println("Got result: " + result) - - assert(result == s"Received: Frodo / Some(hiking) / 33 / Some(${testFile.getName}) (19)") - } - - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) -} diff --git a/examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadPekkoServer.scala index 4285b8b826..eb5b101ab7 100644 --- a/examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadPekkoServer.scala @@ -5,14 +5,14 @@ import java.io.PrintWriter import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Route -import sttp.client3._ -import sttp.tapir.generic.auto._ +import sttp.client3.* +import sttp.tapir.generic.auto.* import sttp.model.Part -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter import scala.concurrent.{Await, Future} -import scala.concurrent.duration._ +import scala.concurrent.duration.* object MultipartFormUploadPekkoServer extends App { implicit val actorSystem: ActorSystem = ActorSystem() @@ -41,7 +41,7 @@ object MultipartFormUploadPekkoServer extends App { }) // starting the server - val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(setProfileRoute).map { _ => + val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(setProfileRoute).map { binding => val testFile = java.io.File.createTempFile("user-123", ".jpg") val pw = new PrintWriter(testFile); pw.write("This is not a photo"); pw.close() @@ -56,7 +56,9 @@ object MultipartFormUploadPekkoServer extends App { println("Got result: " + result) assert(result == s"Received: Frodo / Some(hiking) / 33 / Some(${testFile.getName}) (19)") + + binding } - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) + Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) } diff --git a/examples/src/main/scala/sttp/tapir/examples/observability/DatadogMetricsExample.scala b/examples/src/main/scala/sttp/tapir/examples/observability/DatadogMetricsExample.scala index a8e055d1fa..7a28791c67 100644 --- a/examples/src/main/scala/sttp/tapir/examples/observability/DatadogMetricsExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/observability/DatadogMetricsExample.scala @@ -2,16 +2,16 @@ package sttp.tapir.examples.observability import com.timgroup.statsd.NonBlockingStatsDClientBuilder import com.typesafe.scalalogging.StrictLogging -import io.circe.generic.auto._ -import sttp.tapir._ -import sttp.tapir.generic.auto._ +import io.circe.generic.auto.* +import sttp.tapir.* +import sttp.tapir.generic.auto.* import sttp.tapir.json.circe.jsonBody import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.metrics.datadog.DatadogMetrics import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerOptions} import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration._ +import scala.concurrent.duration.* import scala.concurrent.{Await, Future} import scala.io.StdIn diff --git a/examples/src/main/scala/sttp/tapir/examples/observability/OpenTelemetryMetricsExample.scala b/examples/src/main/scala/sttp/tapir/examples/observability/OpenTelemetryMetricsExample.scala index 2a9bd15bfc..d1b32805a5 100644 --- a/examples/src/main/scala/sttp/tapir/examples/observability/OpenTelemetryMetricsExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/observability/OpenTelemetryMetricsExample.scala @@ -1,21 +1,21 @@ package sttp.tapir.examples.observability import com.typesafe.scalalogging.StrictLogging -import io.circe.generic.auto._ +import io.circe.generic.auto.* import io.opentelemetry.api.OpenTelemetry import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter import io.opentelemetry.sdk.OpenTelemetrySdk import io.opentelemetry.sdk.metrics.SdkMeterProvider import io.opentelemetry.sdk.metrics.`export`.PeriodicMetricReader -import sttp.tapir._ -import sttp.tapir.generic.auto._ +import sttp.tapir.* +import sttp.tapir.generic.auto.* import sttp.tapir.json.circe.jsonBody import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.metrics.opentelemetry.OpenTelemetryMetrics import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerOptions} import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration._ +import scala.concurrent.duration.* import scala.concurrent.{Await, Future} import scala.io.StdIn diff --git a/examples/src/main/scala/sttp/tapir/examples/observability/PrometheusMetricsExample.scala b/examples/src/main/scala/sttp/tapir/examples/observability/PrometheusMetricsExample.scala index acbc2dd73a..f7239b67db 100644 --- a/examples/src/main/scala/sttp/tapir/examples/observability/PrometheusMetricsExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/observability/PrometheusMetricsExample.scala @@ -1,16 +1,16 @@ package sttp.tapir.examples.observability import com.typesafe.scalalogging.StrictLogging -import io.circe.generic.auto._ -import sttp.tapir._ -import sttp.tapir.generic.auto._ -import sttp.tapir.json.circe._ +import io.circe.generic.auto.* +import sttp.tapir.* +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.metrics.prometheus.PrometheusMetrics import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerOptions} import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration._ +import scala.concurrent.duration.* import scala.concurrent.{Await, Future} import scala.io.StdIn @@ -35,12 +35,12 @@ object PrometheusMetricsExample extends App with StrictLogging { .metricsInterceptor(prometheusMetrics.metricsInterceptor()) .options - val endpoints = - List( - personEndpoint, - // Exposes GET endpoint under `metrics` path for prometheus and serializes metrics from `PrometheusRegistry` to plain text response - prometheusMetrics.metricsEndpoint - ) + val endpoints = + List( + personEndpoint, + // Exposes GET endpoint under `metrics` path for prometheus and serializes metrics from `PrometheusRegistry` to plain text response + prometheusMetrics.metricsEndpoint + ) val program = for { binding <- NettyFutureServer().port(8080).addEndpoints(endpoints, serverOptions).start() 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 0b68e3a8fe..79d706ede3 100644 --- a/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala @@ -1,6 +1,6 @@ package sttp.tapir.examples.observability -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.server.interceptor.metrics.MetricsRequestInterceptor import sttp.tapir.server.metrics.zio.ZioMetrics import sttp.tapir.server.ziohttp.{ZioHttpInterpreter, ZioHttpServerOptions} diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationHttp4sServer.scala index 4c1808c6a8..fb035e8cf2 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationHttp4sServer.scala @@ -1,14 +1,14 @@ package sttp.tapir.examples.openapi -import cats.effect._ -import cats.syntax.all._ -import io.circe.generic.auto._ +import cats.effect.* +import cats.syntax.all.* +import io.circe.generic.auto.* import org.http4s.HttpRoutes import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.server.Router -import sttp.tapir._ -import sttp.tapir.generic.auto._ -import sttp.tapir.json.circe._ +import sttp.tapir.* +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* import sttp.tapir.server.http4s.Http4sServerInterpreter import sttp.tapir.swagger.bundle.SwaggerInterpreter diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationAkkaServer.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationPekkoServer.scala similarity index 72% rename from examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationAkkaServer.scala rename to examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationPekkoServer.scala index 4e0e36c2d3..8e64d222a3 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationAkkaServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationPekkoServer.scala @@ -1,19 +1,19 @@ package sttp.tapir.examples.openapi import java.util.concurrent.atomic.AtomicReference -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import io.circe.generic.auto._ -import sttp.tapir.generic.auto._ -import sttp.tapir._ -import sttp.tapir.json.circe._ -import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter +import org.apache.pekko.actor.ActorSystem +import org.apache.pekko.http.scaladsl.Http +import io.circe.generic.auto.* +import sttp.tapir.generic.auto.* +import sttp.tapir.* +import sttp.tapir.json.circe.* +import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter import sttp.tapir.swagger.bundle.SwaggerInterpreter -import scala.concurrent.duration._ +import scala.concurrent.duration.* import scala.concurrent.{Await, Future} -object MultipleEndpointsDocumentationAkkaServer extends App { +object MultipleEndpointsDocumentationPekkoServer extends App { implicit val actorSystem: ActorSystem = ActorSystem() import actorSystem.dispatcher @@ -47,31 +47,33 @@ object MultipleEndpointsDocumentationAkkaServer extends App { ) ) - val booksListingRoute = AkkaHttpServerInterpreter().toRoute(booksListing.serverLogicSuccess(_ => Future.successful(books.get()))) + val booksListingRoute = PekkoHttpServerInterpreter().toRoute(booksListing.serverLogicSuccess(_ => Future.successful(books.get()))) val addBookRoute = - AkkaHttpServerInterpreter().toRoute( + PekkoHttpServerInterpreter().toRoute( addBook.serverLogicSuccess(book => Future.successful { books.getAndUpdate(books => books :+ book); () }) ) // generating and exposing the documentation in yml val swaggerUIRoute = - AkkaHttpServerInterpreter().toRoute( + PekkoHttpServerInterpreter().toRoute( SwaggerInterpreter().fromEndpoints[Future](List(booksListing, addBook), "The tapir library", "1.0.0") ) // starting the server val routes = { - import akka.http.scaladsl.server.Directives._ + import org.apache.pekko.http.scaladsl.server.Directives.* concat(booksListingRoute, addBookRoute, swaggerUIRoute) } - val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(routes).map { _ => + val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(routes).map { binding => // testing println("Go to: http://localhost:8080/docs") println("Press any key to exit ...") scala.io.StdIn.readLine() + + binding } // cleanup - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, Duration.Inf) + Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) } diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/OpenapiExtensions.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/OpenapiExtensions.scala index 377ce94a38..3e975f80a9 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/OpenapiExtensions.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/OpenapiExtensions.scala @@ -1,14 +1,14 @@ package sttp.tapir.examples.openapi -import io.circe.generic.auto._ +import io.circe.generic.auto.* import sttp.apispec.openapi.Info -import sttp.apispec.openapi.circe.yaml._ -import sttp.tapir._ +import sttp.apispec.openapi.circe.yaml.* +import sttp.tapir.* import sttp.tapir.docs.apispec.DocsExtension import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter -import sttp.tapir.docs.apispec.DocsExtensionAttribute._ -import sttp.tapir.generic.auto._ -import sttp.tapir.json.circe._ +import sttp.tapir.docs.apispec.DocsExtensionAttribute.* +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* object OpenapiExtensions extends App { diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/RedocContextPathHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/RedocContextPathHttp4sServer.scala index 7f2615962b..d250989951 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/RedocContextPathHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/RedocContextPathHttp4sServer.scala @@ -1,11 +1,11 @@ package sttp.tapir.examples.openapi -import cats.effect._ -import cats.syntax.all._ +import cats.effect.* +import cats.syntax.all.* import org.http4s.HttpRoutes import org.http4s.server.Router import org.http4s.blaze.server.BlazeServerBuilder -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.redoc.RedocUIOptions import sttp.tapir.redoc.bundle.RedocInterpreter import sttp.tapir.server.http4s.Http4sServerInterpreter 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 868e41623c..c33b012f97 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala @@ -1,11 +1,11 @@ package sttp.tapir.examples.openapi -import io.circe.generic.auto._ -import sttp.tapir.generic.auto._ -import sttp.tapir.json.circe._ +import io.circe.generic.auto.* +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* import sttp.tapir.redoc.bundle.RedocInterpreter import sttp.tapir.server.ziohttp.ZioHttpInterpreter -import sttp.tapir.ztapir._ +import sttp.tapir.ztapir.* import zio.http.HttpApp import zio.http.Server import zio.Console.{printLine, readLine} diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/SwaggerUIOAuth2AkkaServer.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/SwaggerUIOAuth2PekkoServer.scala similarity index 84% rename from examples/src/main/scala/sttp/tapir/examples/openapi/SwaggerUIOAuth2AkkaServer.scala rename to examples/src/main/scala/sttp/tapir/examples/openapi/SwaggerUIOAuth2PekkoServer.scala index 4adab78070..a59da47069 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/SwaggerUIOAuth2AkkaServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/SwaggerUIOAuth2PekkoServer.scala @@ -1,11 +1,11 @@ package sttp.tapir.examples.openapi -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.server.{Route, RouteConcatenation} -import sttp.tapir._ +import org.apache.pekko.actor.ActorSystem +import org.apache.pekko.http.scaladsl.Http +import org.apache.pekko.http.scaladsl.server.{Route, RouteConcatenation} +import sttp.tapir.* import sttp.tapir.server.PartialServerEndpoint -import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter +import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter import sttp.tapir.swagger.bundle.SwaggerInterpreter import scala.concurrent.duration.DurationInt @@ -28,7 +28,7 @@ import scala.concurrent.{Await, Future, Promise} * * Go to: [[http://localhost:3333/docs]] And try authorize by using `Authorize` by providing details of clients and user */ -object SwaggerUIOAuth2AkkaServer extends App with RouteConcatenation { +object SwaggerUIOAuth2PekkoServer extends App with RouteConcatenation { implicit val actorSystem: ActorSystem = ActorSystem() import actorSystem.dispatcher @@ -56,14 +56,14 @@ object SwaggerUIOAuth2AkkaServer extends App with RouteConcatenation { .serverLogic(_ => word => countCharacters(word)) val countCharactersRoute: Route = - AkkaHttpServerInterpreter().toRoute(countCharactersEndpoint) + PekkoHttpServerInterpreter().toRoute(countCharactersEndpoint) val endpoints: List[AnyEndpoint] = List(countCharactersEndpoint).map(_.endpoint) val swaggerEndpoints = SwaggerInterpreter() .fromEndpoints[Future](endpoints, "My App", "1.0") - val swaggerRoute: Route = AkkaHttpServerInterpreter().toRoute(swaggerEndpoints) + val swaggerRoute: Route = PekkoHttpServerInterpreter().toRoute(swaggerEndpoints) val routes = countCharactersRoute ~ swaggerRoute diff --git a/examples/src/main/scala/sttp/tapir/examples/schema/CustomisingSchemas.scala b/examples/src/main/scala/sttp/tapir/examples/schema/CustomisingSchemas.scala index 2e60748cc3..9ed981f9df 100644 --- a/examples/src/main/scala/sttp/tapir/examples/schema/CustomisingSchemas.scala +++ b/examples/src/main/scala/sttp/tapir/examples/schema/CustomisingSchemas.scala @@ -1,11 +1,11 @@ package sttp.tapir.examples.schema -import io.circe.generic.auto._ +import io.circe.generic.auto.* import sttp.tapir.Schema.annotations.{description, validateEach} -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.generic.Derived -import sttp.tapir.generic.auto._ -import sttp.tapir.json.circe._ +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding} import sttp.tapir.swagger.bundle.SwaggerInterpreter diff --git a/examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationAkkaServer.scala b/examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationAkkaServer.scala deleted file mode 100644 index 3dbf7685a7..0000000000 --- a/examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationAkkaServer.scala +++ /dev/null @@ -1,46 +0,0 @@ -package sttp.tapir.examples.security - -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.server.Route -import sttp.client3._ -import sttp.model.StatusCode -import sttp.model.headers.WWWAuthenticateChallenge -import sttp.tapir._ -import sttp.tapir.model._ -import sttp.tapir.server.akkahttp._ - -import scala.concurrent.duration._ -import scala.concurrent.{Await, Future} - -object BasicAuthenticationAkkaServer extends App { - implicit val actorSystem: ActorSystem = ActorSystem() - import actorSystem.dispatcher - - val secret: Endpoint[UsernamePassword, Unit, Unit, String, Any] = - endpoint.get.securityIn("secret").securityIn(auth.basic[UsernamePassword](WWWAuthenticateChallenge.basic("example"))).out(stringBody) - - val secretRoute: Route = - AkkaHttpServerInterpreter().toRoute( - secret - .serverSecurityLogic(credentials => Future.successful(Right(credentials.username): Either[Unit, String])) - .serverLogic(username => _ => Future.successful(Right(s"Hello, $username!"))) - ) - - // starting the server - val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(secretRoute).map { _ => - // testing - val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() - val unauthorized = basicRequest.get(uri"http://localhost:8080/secret").send(backend) - println("Got result: " + unauthorized) - assert(unauthorized.code == StatusCode.Unauthorized) - assert(unauthorized.header("WWW-Authenticate").contains("""Basic realm="example"""")) - - val result = basicRequest.get(uri"http://localhost:8080/secret").header("Authorization", "Basic dXNlcjpzZWNyZXQ=").send(backend) - println("Got result: " + result) - assert(result.code == StatusCode.Ok) - assert(result.body == Right("Hello, user!")) - } - - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) -} diff --git a/examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationPekkoServer.scala index d82aca2eee..504f74cb1f 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationPekkoServer.scala @@ -3,14 +3,14 @@ package sttp.tapir.examples.security import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Route -import sttp.client3._ +import sttp.client3.* import sttp.model.StatusCode import sttp.model.headers.WWWAuthenticateChallenge -import sttp.tapir._ -import sttp.tapir.model._ -import sttp.tapir.server.pekkohttp._ +import sttp.tapir.* +import sttp.tapir.model.* +import sttp.tapir.server.pekkohttp.* -import scala.concurrent.duration._ +import scala.concurrent.duration.* import scala.concurrent.{Await, Future} object BasicAuthenticationPekkoServer extends App { @@ -28,7 +28,7 @@ object BasicAuthenticationPekkoServer extends App { ) // starting the server - val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(secretRoute).map { _ => + val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(secretRoute).map { binding => // testing val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() val unauthorized = basicRequest.get(uri"http://localhost:8080/secret").send(backend) @@ -40,7 +40,9 @@ object BasicAuthenticationPekkoServer extends App { println("Got result: " + result) assert(result.code == StatusCode.Ok) assert(result.body == Right("Hello, user!")) + + binding } - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) + Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) } diff --git a/examples/src/main/scala/sttp/tapir/examples/security/ExternalSecurityInterceptor.scala b/examples/src/main/scala/sttp/tapir/examples/security/ExternalSecurityInterceptor.scala index ba607be3a4..dc6f298813 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/ExternalSecurityInterceptor.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/ExternalSecurityInterceptor.scala @@ -1,10 +1,10 @@ package sttp.tapir.examples.security -import sttp.client3._ +import sttp.client3.* import sttp.model.StatusCode import sttp.monad.MonadError import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding, NettyFutureServerOptions} -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.server.interceptor.{ DecodeFailureContext, DecodeSuccessContext, diff --git a/examples/src/main/scala/sttp/tapir/examples/security/OAuth2GithubHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/security/OAuth2GithubHttp4sServer.scala index db589b099e..a878914e81 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/OAuth2GithubHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/OAuth2GithubHttp4sServer.scala @@ -1,18 +1,18 @@ package sttp.tapir.examples.security -import cats.effect._ -import cats.syntax.all._ -import io.circe.generic.auto._ +import cats.effect.* +import cats.syntax.all.* +import io.circe.generic.auto.* import org.http4s.HttpRoutes import org.http4s.server.Router import org.http4s.blaze.server.BlazeServerBuilder import pdi.jwt.{JwtAlgorithm, JwtCirce, JwtClaim} -import sttp.client3._ +import sttp.client3.* import sttp.client3.asynchttpclient.cats.AsyncHttpClientCatsBackend import sttp.model.StatusCode -import sttp.tapir._ -import sttp.tapir.generic.auto._ -import sttp.tapir.json.circe._ +import sttp.tapir.* +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* import sttp.tapir.server.http4s.Http4sServerInterpreter import java.time.Instant diff --git a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicAkka.scala b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicAkka.scala deleted file mode 100644 index f107f161c2..0000000000 --- a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicAkka.scala +++ /dev/null @@ -1,89 +0,0 @@ -package sttp.tapir.examples.security - -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.server.Directives._ -import akka.http.scaladsl.server.Route -import sttp.client3._ -import sttp.model.HeaderNames -import sttp.tapir._ -import sttp.tapir.server.{PartialServerEndpoint, ServerEndpoint} -import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter - -import scala.concurrent.duration._ -import scala.concurrent.{Await, Future} - -object ServerSecurityLogicAkka extends App { - implicit val actorSystem: ActorSystem = ActorSystem() - import actorSystem.dispatcher - - // authentication data structure & logic - case class User(name: String) - case class AuthenticationToken(value: String) - case class AuthenticationError(code: Int) - - def authenticate(token: AuthenticationToken): Future[Either[AuthenticationError, User]] = - Future { - if (token.value == "berries") Right(User("Papa Smurf")) - else if (token.value == "smurf") Right(User("Gargamel")) - else Left(AuthenticationError(1001)) - } - - // defining a base endpoint, which has the authentication logic built-in - val secureEndpoint: PartialServerEndpoint[AuthenticationToken, User, Unit, AuthenticationError, Unit, Any, Future] = endpoint - .securityIn(auth.bearer[String]().mapTo[AuthenticationToken]) - // returning the authentication error code to the user - .errorOut(plainBody[Int].mapTo[AuthenticationError]) - .serverSecurityLogic(authenticate) - - // the errors that might occur in the /hello endpoint - either a wrapped authentication error, or refusal to greet - sealed trait HelloError - case class AuthenticationHelloError(wrapped: AuthenticationError) extends HelloError - case class NoHelloError(why: String) extends HelloError - - // extending the base endpoint with hello-endpoint-specific inputs - val secureHelloWorldWithLogic: ServerEndpoint[Any, Future] = secureEndpoint.get - .in("hello") - .in(query[String]("salutation")) - .out(stringBody) - .mapErrorOut(AuthenticationHelloError)(_.wrapped) - // returning a 400 with the "why" field from the exception - .errorOutVariant[HelloError](oneOfVariant(stringBody.mapTo[NoHelloError])) - // defining the remaining server logic (which uses the authenticated user) - .serverLogic { user => salutation => - Future( - if (user.name == "Gargamel") Left(NoHelloError(s"Not saying hello to ${user.name}!")) - else Right(s"$salutation, ${user.name}!") - ) - } - - // --- - - // interpreting as routes - val helloWorld1Route: Route = AkkaHttpServerInterpreter().toRoute(secureHelloWorldWithLogic) - - // starting the server - val bindAndCheck = Http().newServerAt("localhost", 8080).bind(concat(helloWorld1Route)).map { _ => - // testing - val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() - - def testWith(path: String, salutation: String, token: String): String = { - val result: String = basicRequest - .response(asStringAlways) - .get(uri"http://localhost:8080/$path?salutation=$salutation") - .header(HeaderNames.Authorization, s"Bearer $token") - .send(backend) - .body - - println(s"For path: $path, salutation: $salutation, token: $token, got result: $result") - result - } - - assert(testWith("hello", "Hello", "berries") == "Hello, Papa Smurf!") - assert(testWith("hello", "Cześć", "berries") == "Cześć, Papa Smurf!") - assert(testWith("hello", "Hello", "apple") == "1001") - assert(testWith("hello", "Hello", "smurf") == "Not saying hello to Gargamel!") - } - - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) -} diff --git a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicPekko.scala b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicPekko.scala index adfbcee2b9..0b8ab743ff 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicPekko.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicPekko.scala @@ -2,15 +2,15 @@ package sttp.tapir.examples.security import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http -import org.apache.pekko.http.scaladsl.server.Directives._ +import org.apache.pekko.http.scaladsl.server.Directives.* import org.apache.pekko.http.scaladsl.server.Route -import sttp.client3._ +import sttp.client3.* import sttp.model.HeaderNames -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.server.{PartialServerEndpoint, ServerEndpoint} import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter -import scala.concurrent.duration._ +import scala.concurrent.duration.* import scala.concurrent.{Await, Future} object ServerSecurityLogicPekko extends App { @@ -63,7 +63,7 @@ object ServerSecurityLogicPekko extends App { val helloWorld1Route: Route = PekkoHttpServerInterpreter().toRoute(secureHelloWorldWithLogic) // starting the server - val bindAndCheck = Http().newServerAt("localhost", 8080).bind(concat(helloWorld1Route)).map { _ => + val bindAndCheck = Http().newServerAt("localhost", 8080).bind(concat(helloWorld1Route)).map { binding => // testing val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() @@ -83,7 +83,9 @@ object ServerSecurityLogicPekko extends App { assert(testWith("hello", "Cześć", "berries") == "Cześć, Papa Smurf!") assert(testWith("hello", "Hello", "apple") == "1001") assert(testWith("hello", "Hello", "smurf") == "Not saying hello to Gargamel!") + + binding } - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) + Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) } diff --git a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicRefreshCookiesAkka.scala b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicRefreshCookiesAkka.scala deleted file mode 100644 index 2f87738e2b..0000000000 --- a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicRefreshCookiesAkka.scala +++ /dev/null @@ -1,70 +0,0 @@ -package sttp.tapir.examples.security - -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.server.Directives.concat -import akka.http.scaladsl.server.Route -import sttp.client3._ -import sttp.model.StatusCode -import sttp.model.headers.CookieValueWithMeta -import sttp.tapir._ -import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter -import sttp.tapir.server.{PartialServerEndpointWithSecurityOutput, ServerEndpoint} - -import scala.concurrent.duration._ -import scala.concurrent.{Await, Future} - -object ServerSecurityLogicRefreshCookiesAkka extends App { - implicit val actorSystem: ActorSystem = ActorSystem() - import actorSystem.dispatcher - - case class User(name: String) - - // we're always refreshing the cookie - def authenticate(token: Option[String]): Either[Unit, (Option[CookieValueWithMeta], User)] = - token match { - case None => Left(()) - case Some(token) => Right((Some(CookieValueWithMeta.unsafeApply("new token")), User(token))) - } - - // defining a base endpoint, which has the authentication logic built-in - val secureEndpoint - : PartialServerEndpointWithSecurityOutput[Option[String], User, Unit, Unit, Option[CookieValueWithMeta], Unit, Any, Future] = endpoint - .securityIn(auth.apiKey(cookie[Option[String]]("token"))) - // optionally returning a refreshed cookie - .out(setCookieOpt("token")) - .errorOut(statusCode(StatusCode.Unauthorized)) - .serverSecurityLogicPureWithOutput(authenticate) - - // extending the base endpoint with hello-endpoint-specific inputs - val secureHelloWorldWithLogic: ServerEndpoint[Any, Future] = secureEndpoint.get - .in("hello") - .in(query[String]("salutation")) - .out(stringBody) - // defining the remaining server logic (which uses the authenticated user) - .serverLogic { user => salutation => - Future(Right(s"$salutation, ${user.name}!")) - } - - // --- - - // interpreting as routes - val helloWorld1Route: Route = AkkaHttpServerInterpreter().toRoute(secureHelloWorldWithLogic) - - // starting the server - val bindAndCheck = Http().newServerAt("localhost", 8080).bind(concat(helloWorld1Route)).map { _ => - // testing - val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() - - val response = basicRequest - .response(asStringAlways) - .cookie("token", "Steve") - .get(uri"http://localhost:8080/hello?salutation=Welcome") - .send(backend) - - assert(response.body == "Welcome, Steve!") - assert(response.unsafeCookies.map(_.value).toList == List("new token")) - } - - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) -} diff --git a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicRefreshCookiesPekko.scala b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicRefreshCookiesPekko.scala index b6f59316b6..7581fc8246 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicRefreshCookiesPekko.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicRefreshCookiesPekko.scala @@ -4,14 +4,14 @@ import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Directives.concat import org.apache.pekko.http.scaladsl.server.Route -import sttp.client3._ +import sttp.client3.* import sttp.model.StatusCode import sttp.model.headers.CookieValueWithMeta -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter import sttp.tapir.server.{PartialServerEndpointWithSecurityOutput, ServerEndpoint} -import scala.concurrent.duration._ +import scala.concurrent.duration.* import scala.concurrent.{Await, Future} object ServerSecurityLogicRefreshCookiesPekko extends App { @@ -52,7 +52,7 @@ object ServerSecurityLogicRefreshCookiesPekko extends App { val helloWorld1Route: Route = PekkoHttpServerInterpreter().toRoute(secureHelloWorldWithLogic) // starting the server - val bindAndCheck = Http().newServerAt("localhost", 8080).bind(concat(helloWorld1Route)).map { _ => + val bindAndCheck = Http().newServerAt("localhost", 8080).bind(concat(helloWorld1Route)).map { binding => // testing val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() @@ -64,7 +64,9 @@ object ServerSecurityLogicRefreshCookiesPekko extends App { assert(response.body == "Welcome, Steve!") assert(response.unsafeCookies.map(_.value).toList == List("new token")) + + binding } - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) + Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) } diff --git a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesAkkaServer.scala b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesAkkaServer.scala deleted file mode 100644 index 3be642ac2c..0000000000 --- a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesAkkaServer.scala +++ /dev/null @@ -1,54 +0,0 @@ -package sttp.tapir.examples.static_content - -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.server.Route -import sttp.client3._ -import sttp.model.{ContentRangeUnits, Header, HeaderNames, StatusCode} -import sttp.tapir._ -import sttp.tapir.files._ -import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter - -import java.nio.file.{Files, Path, StandardOpenOption} -import scala.concurrent.duration.DurationInt -import scala.concurrent.{Await, Future} - -object StaticContentFromFilesAkkaServer extends App { - implicit val actorSystem: ActorSystem = ActorSystem() - import actorSystem.dispatcher - - val content = "f1 content" - val exampleDirectory: Path = Files.createTempDirectory("akka-static-example") - Files.write(exampleDirectory.resolve("f1"), content.getBytes, StandardOpenOption.CREATE_NEW) - - val fileEndpoints = staticFilesServerEndpoints[Future]("range-example")(exampleDirectory.toFile.getAbsolutePath) - val route: Route = AkkaHttpServerInterpreter().toRoute(fileEndpoints) - - // starting the server - val bindAndCheck: Future[Unit] = Http().newServerAt("localhost", 8080).bindFlow(route).map { _ => - // testing - val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() - val headResponse = basicRequest - .head(uri"http://localhost:8080/range-example/f1") - .response(asStringAlways) - .send(backend) - - assert(headResponse.code == StatusCode.Ok) - assert(headResponse.headers.contains(Header(HeaderNames.AcceptRanges, ContentRangeUnits.Bytes))) - assert(headResponse.headers.contains(Header(HeaderNames.ContentLength, content.length.toString))) - - val getResponse = basicRequest - .headers(Header(HeaderNames.Range, "bytes=3-6")) - .get(uri"http://localhost:8080/range-example/f1") - .response(asStringAlways) - .send(backend) - - assert(getResponse.body == "cont") - assert(getResponse.code == StatusCode.PartialContent) - assert(getResponse.body.length == 4) - assert(getResponse.headers.contains(Header(HeaderNames.ContentRange, "bytes 3-6/10"))) - - } - - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) -} diff --git a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesNettyServer.scala b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesNettyServer.scala index 9d31b266a6..d1e80ef4f9 100644 --- a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesNettyServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesNettyServer.scala @@ -2,7 +2,7 @@ package sttp.tapir.examples.static_content import sttp.tapir.server.netty.NettyFutureServer import sttp.tapir.emptyInput -import sttp.tapir.files._ +import sttp.tapir.files.* import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future diff --git a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesPekkoServer.scala index 42e3c03e7c..08617f8785 100644 --- a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesPekkoServer.scala @@ -3,10 +3,10 @@ package sttp.tapir.examples.static_content import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Route -import sttp.client3._ +import sttp.client3.* import sttp.model.{ContentRangeUnits, Header, HeaderNames, StatusCode} -import sttp.tapir._ -import sttp.tapir.files._ +import sttp.tapir.* +import sttp.tapir.files.* import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter import java.nio.file.{Files, Path, StandardOpenOption} @@ -25,7 +25,7 @@ object StaticContentFromFilesPekkoServer extends App { val route: Route = PekkoHttpServerInterpreter().toRoute(fileEndpoints) // starting the server - val bindAndCheck: Future[Unit] = Http().newServerAt("localhost", 8080).bindFlow(route).map { _ => + val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(route).map { binding => // testing val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() val headResponse = basicRequest @@ -48,7 +48,8 @@ object StaticContentFromFilesPekkoServer extends App { assert(getResponse.body.length == 4) assert(getResponse.headers.contains(Header(HeaderNames.ContentRange, "bytes 3-6/10"))) + binding } - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) + Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) } diff --git a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromResourcesAkkaServer.scala b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromResourcesAkkaServer.scala deleted file mode 100644 index 9ab0ce693b..0000000000 --- a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromResourcesAkkaServer.scala +++ /dev/null @@ -1,33 +0,0 @@ -package sttp.tapir.examples.static_content - -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.server.Route -import sttp.tapir._ -import sttp.tapir.files._ -import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter - -import scala.concurrent.duration.DurationInt -import scala.concurrent.{Await, Future} -import scala.io.StdIn - -object StaticContentFromResourcesAkkaServer extends App { - implicit val actorSystem: ActorSystem = ActorSystem() - import actorSystem.dispatcher - - // we're pretending to be a SPA application, that is we serve index.html if the requested resource cannot be found - val resourceEndpoints = staticResourcesGetServerEndpoint[Future](emptyInput)( - StaticContentFromResourcesAkkaServer.getClass.getClassLoader, - "webapp", - FilesOptions.default.defaultFile(List("index.html")) - ) - val route: Route = AkkaHttpServerInterpreter().toRoute(resourceEndpoints) - - // starting the server - val bind = Http().newServerAt("localhost", 8080).bindFlow(route) - Await.result(bind, 1.minute) - println("Open: http://localhost:8080 and experiment with various paths.") - println("Press any key to exit ...") - StdIn.readLine() - Await.result(actorSystem.terminate(), 1.minute) -} diff --git a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromResourcesPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromResourcesPekkoServer.scala index 82579de4b5..b8f8c2673f 100644 --- a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromResourcesPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromResourcesPekkoServer.scala @@ -3,8 +3,8 @@ package sttp.tapir.examples.static_content import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Route -import sttp.tapir._ -import sttp.tapir.files._ +import sttp.tapir.* +import sttp.tapir.files.* import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter import scala.concurrent.duration.DurationInt diff --git a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentSecureAkkaServer.scala b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentSecureAkkaServer.scala deleted file mode 100644 index 002608d557..0000000000 --- a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentSecureAkkaServer.scala +++ /dev/null @@ -1,58 +0,0 @@ -package sttp.tapir.examples.static_content - -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.server.Route -import sttp.client3._ -import sttp.model.StatusCode -import sttp.tapir._ -import sttp.tapir.files._ -import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter - -import java.nio.file.{Files, Path, StandardOpenOption} -import scala.concurrent.duration.DurationInt -import scala.concurrent.{Await, Future} - -object StaticContentSecureAkkaServer extends App { - // creating test files - val exampleDirectory: Path = Files.createTempDirectory("akka-static-secure-example") - Files.write(exampleDirectory.resolve("f1"), "f1 content".getBytes, StandardOpenOption.CREATE_NEW) - - implicit val actorSystem: ActorSystem = ActorSystem() - import actorSystem.dispatcher - - // defining the endpoints - val secureFileEndpoints = staticFilesServerEndpoints[Future]("secure")(exampleDirectory.toFile.getAbsolutePath) - .map(_.prependSecurityPure(auth.bearer[String](), statusCode(StatusCode.Forbidden)) { token => - // Right means success, Left - an error, here mapped to a constant status code - if (token.startsWith("secret")) Right(()) else Left(()) - }) - - // starting the server - val route: Route = AkkaHttpServerInterpreter().toRoute(secureFileEndpoints) - - val bindAndCheck: Future[Unit] = Http().newServerAt("localhost", 8080).bindFlow(route).map { _ => - // testing - val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() - val response1 = basicRequest - .get(uri"http://localhost:8080/secure/f1") - .auth - .bearer("hacker") - .response(asStringAlways) - .send(backend) - - assert(response1.code == StatusCode.Forbidden) - - val response2 = basicRequest - .get(uri"http://localhost:8080/secure/f1") - .auth - .bearer("secret123") - .response(asStringAlways) - .send(backend) - - assert(response2.code == StatusCode.Ok) - assert(response2.body == "f1 content") - } - - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) -} diff --git a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentSecurePekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentSecurePekkoServer.scala index 2b860d8d90..a476671ce9 100644 --- a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentSecurePekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentSecurePekkoServer.scala @@ -3,10 +3,10 @@ package sttp.tapir.examples.static_content import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Route -import sttp.client3._ +import sttp.client3.* import sttp.model.StatusCode -import sttp.tapir._ -import sttp.tapir.files._ +import sttp.tapir.* +import sttp.tapir.files.* import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter import java.nio.file.{Files, Path, StandardOpenOption} @@ -31,7 +31,7 @@ object StaticContentSecurePekkoServer extends App { // starting the server val route: Route = PekkoHttpServerInterpreter().toRoute(secureFileEndpoints) - val bindAndCheck: Future[Unit] = Http().newServerAt("localhost", 8080).bindFlow(route).map { _ => + val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(route).map { binding => // testing val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() val response1 = basicRequest @@ -52,7 +52,9 @@ object StaticContentSecurePekkoServer extends App { assert(response2.code == StatusCode.Ok) assert(response2.body == "f1 content") + + binding } - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) + Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) } 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 index 45c7cf457e..457b4332e2 100644 --- a/examples/src/main/scala/sttp/tapir/examples/status_code/StatusCodeNettyFutureServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/status_code/StatusCodeNettyFutureServer.scala @@ -1,7 +1,7 @@ package sttp.tapir.examples.status_code import sttp.model.{HeaderNames, StatusCode} -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding} import scala.concurrent.{Await, Future} diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala index 2ec40937e7..2cba2b12e8 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala @@ -7,10 +7,10 @@ import org.http4s.HttpRoutes import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.server.Router import sttp.capabilities.fs2.Fs2Streams -import sttp.client3._ +import sttp.client3.* import sttp.client3.httpclient.fs2.HttpClientFs2Backend import sttp.model.{Header, HeaderNames, Method, QueryParams} -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.server.http4s.Http4sServerInterpreter /** Proxies requests from /proxy to https://httpbin.org/anything */ diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingAkkaServer.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingAkkaServer.scala deleted file mode 100644 index 561e88eb9f..0000000000 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingAkkaServer.scala +++ /dev/null @@ -1,41 +0,0 @@ -package sttp.tapir.examples.streaming - -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.server.Route -import akka.stream.scaladsl.Source -import akka.util.ByteString -import sttp.capabilities.akka.AkkaStreams -import sttp.client3._ -import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter -import sttp.tapir._ - -import scala.concurrent.{Await, Future} -import scala.concurrent.duration._ - -object StreamingAkkaServer extends App { - implicit val actorSystem: ActorSystem = ActorSystem() - import actorSystem.dispatcher - - // The endpoint: corresponds to GET /receive. - // We need to provide both the schema of the value (for documentation), as well as the format (media type) of the - // body. Here, the schema is a `string` and the media type is `text/plain`. - val streamingEndpoint: PublicEndpoint[Unit, Unit, Source[ByteString, Any], AkkaStreams] = - endpoint.get.in("receive").out(streamTextBody(AkkaStreams)(CodecFormat.TextPlain())) - - // converting an endpoint to a route (providing server-side logic); extension method comes from imported packages - val testStream: Source[ByteString, Any] = Source.repeat("Hello!").take(10).map(s => ByteString(s)) - val streamingRoute: Route = AkkaHttpServerInterpreter().toRoute(streamingEndpoint.serverLogicSuccess(_ => Future.successful(testStream))) - - // starting the server - val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(streamingRoute).map { _ => - // testing - val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() - val result: String = basicRequest.response(asStringAlways).get(uri"http://localhost:8080/receive").send(backend).body - println("Got result: " + result) - - assert(result == "Hello!" * 10) - } - - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) -} diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala index 86d9a6afcc..6b7f744876 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala @@ -1,19 +1,19 @@ package sttp.tapir.examples.streaming import cats.effect.{ExitCode, IO, IOApp} -import cats.implicits._ +import cats.implicits.* import fs2.{Chunk, Stream} import org.http4s.HttpRoutes import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.server.Router import sttp.capabilities.fs2.Fs2Streams -import sttp.client3._ +import sttp.client3.* import sttp.model.HeaderNames -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.server.http4s.Http4sServerInterpreter import java.nio.charset.StandardCharsets -import scala.concurrent.duration._ +import scala.concurrent.duration.* // https://github.com/softwaremill/tapir/issues/367 object StreamingHttp4sFs2Server extends IOApp { diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala index a7eb446d6a..09fe290688 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala @@ -1,17 +1,17 @@ package sttp.tapir.examples.streaming import cats.effect.{ExitCode, IO, IOApp} -import cats.implicits._ +import cats.implicits.* import fs2.{Chunk, Stream} import sttp.capabilities.fs2.Fs2Streams -import sttp.client3._ +import sttp.client3.* import sttp.model.HeaderNames -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.netty.cats.{NettyCatsServer, NettyCatsServerBinding} import java.nio.charset.StandardCharsets -import scala.concurrent.duration._ +import scala.concurrent.duration.* object StreamingNettyFs2Server extends IOApp { // corresponds to: GET /receive?name=... diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyZioServer.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyZioServer.scala index 6692b40a6a..f9b78a4b8d 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyZioServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyZioServer.scala @@ -1,14 +1,14 @@ package sttp.tapir.examples.streaming import sttp.capabilities.zio.ZioStreams -import sttp.client3._ +import sttp.client3.* import sttp.model.HeaderNames import sttp.tapir.{CodecFormat, PublicEndpoint} import sttp.tapir.server.netty.zio.NettyZioServer -import sttp.tapir.ztapir._ -import zio.interop.catz._ -import zio._ -import zio.stream._ +import sttp.tapir.ztapir.* +import zio.interop.catz.* +import zio.* +import zio.stream.* import java.nio.charset.StandardCharsets diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingPekkoServer.scala index f057292f90..da83fd8d77 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingPekkoServer.scala @@ -6,12 +6,12 @@ import org.apache.pekko.http.scaladsl.server.Route import org.apache.pekko.stream.scaladsl.Source import org.apache.pekko.util.ByteString import sttp.capabilities.pekko.PekkoStreams -import sttp.client3._ +import sttp.client3.* import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter -import sttp.tapir._ +import sttp.tapir.* import scala.concurrent.{Await, Future} -import scala.concurrent.duration._ +import scala.concurrent.duration.* object StreamingPekkoServer extends App { implicit val actorSystem: ActorSystem = ActorSystem() @@ -28,14 +28,16 @@ object StreamingPekkoServer extends App { val streamingRoute: Route = PekkoHttpServerInterpreter().toRoute(streamingEndpoint.serverLogicSuccess(_ => Future.successful(testStream))) // starting the server - val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(streamingRoute).map { _ => + val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(streamingRoute).map { binding => // testing val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() val result: String = basicRequest.response(asStringAlways).get(uri"http://localhost:8080/receive").send(backend).body println("Got result: " + result) assert(result == "Hello!" * 10) + + binding } - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) + Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) } 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 933a15d593..db100dc512 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala @@ -3,12 +3,12 @@ package sttp.tapir.examples.streaming import sttp.capabilities.zio.ZioStreams import sttp.model.HeaderNames import sttp.tapir.{CodecFormat, PublicEndpoint} -import sttp.tapir.ztapir._ +import sttp.tapir.ztapir.* import sttp.tapir.server.ziohttp.ZioHttpInterpreter import zio.http.HttpApp import zio.http.Server import zio.{ExitCode, Schedule, URIO, ZIO, ZIOAppDefault, ZLayer} -import zio.stream._ +import zio.stream.* import java.nio.charset.StandardCharsets import java.time.Duration diff --git a/examples/src/main/scala/sttp/tapir/examples/testing/AkkaServerStubInterpreterExample.scala b/examples/src/main/scala/sttp/tapir/examples/testing/AkkaServerStubInterpreterExample.scala deleted file mode 100644 index 14dded56b0..0000000000 --- a/examples/src/main/scala/sttp/tapir/examples/testing/AkkaServerStubInterpreterExample.scala +++ /dev/null @@ -1,67 +0,0 @@ -package sttp.tapir.examples.testing - -import org.scalatest.flatspec.AsyncFlatSpec -import org.scalatest.matchers.should.Matchers -import sttp.client3._ -import sttp.client3.testing.SttpBackendStub -import sttp.model.StatusCode -import sttp.tapir._ -import sttp.tapir.server.ServerEndpoint -import sttp.tapir.server.akkahttp.AkkaHttpServerOptions -import sttp.tapir.server.interceptor.exception.ExceptionHandler -import sttp.tapir.server.interceptor.CustomiseInterceptors -import sttp.tapir.server.model.ValuedEndpointOutput -import sttp.tapir.server.stub.TapirStubInterpreter - -import scala.concurrent.{ExecutionContext, Future} - -class AkkaServerStubInterpreterExample extends AsyncFlatSpec with Matchers { - - it should "use custom exception handler" in { - val stubBackend: SttpBackend[Future, Any] = TapirStubInterpreter(UsersApi.options, SttpBackendStub.asynchronousFuture) - .whenServerEndpoint(UsersApi.greetUser) - .thenThrowException(new RuntimeException("error")) - .backend() - - sttp.client3.basicRequest - .get(uri"http://test.com/api/users/greet") - .send(stubBackend) - .map(_.body shouldBe Left("failed due to error")) - } - - it should "run greet users logic" in { - val stubBackend: SttpBackend[Future, Any] = TapirStubInterpreter(UsersApi.options, SttpBackendStub.asynchronousFuture) - .whenServerEndpoint(UsersApi.greetUser) - .thenRunLogic() - .backend() - - val response = sttp.client3.basicRequest - .get(uri"http://test.com/api/users/greet") - .header("Authorization", "Bearer password") - .send(stubBackend) - - // then - response.map(_.body shouldBe Right("hello user123")) - } -} - -object UsersApi { - - val greetUser: ServerEndpoint[Any, Future] = endpoint.get - .in("api" / "users" / "greet") - .securityIn(auth.bearer[String]()) - .out(stringBody) - .errorOut(stringBody) - .serverSecurityLogic(token => - Future.successful { - if (token == "password") Right("user123") else Left("unauthorized") - } - ) - .serverLogic(user => _ => Future.successful(Right(s"hello $user"))) - - val exceptionHandler = ExceptionHandler.pure[Future](ctx => - Option(ValuedEndpointOutput(stringBody.and(statusCode), (s"failed due to ${ctx.e.getMessage}", StatusCode.InternalServerError))) - ) - def options(implicit ec: ExecutionContext): CustomiseInterceptors[Future, AkkaHttpServerOptions] = - AkkaHttpServerOptions.customiseInterceptors.exceptionHandler(exceptionHandler) -} diff --git a/examples/src/main/scala/sttp/tapir/examples/testing/PekkoServerStubInterpreterExample.scala b/examples/src/main/scala/sttp/tapir/examples/testing/PekkoServerStubInterpreterExample.scala index 768e53429c..b27357cfa9 100644 --- a/examples/src/main/scala/sttp/tapir/examples/testing/PekkoServerStubInterpreterExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/testing/PekkoServerStubInterpreterExample.scala @@ -2,10 +2,10 @@ package sttp.tapir.examples.testing import org.scalatest.flatspec.AsyncFlatSpec import org.scalatest.matchers.should.Matchers -import sttp.client3._ +import sttp.client3.* import sttp.client3.testing.SttpBackendStub import sttp.model.StatusCode -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.pekkohttp.PekkoHttpServerOptions import sttp.tapir.server.interceptor.exception.ExceptionHandler diff --git a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketAkkaServer.scala b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketAkkaServer.scala deleted file mode 100644 index 27fc1d683f..0000000000 --- a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketAkkaServer.scala +++ /dev/null @@ -1,73 +0,0 @@ -package sttp.tapir.examples.websocket - -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.server.Route -import akka.stream.scaladsl.Flow -import io.circe.generic.auto._ -import sttp.tapir.generic.auto._ -import sttp.capabilities.WebSockets -import sttp.capabilities.akka.AkkaStreams -import sttp.client3._ -import sttp.client3.akkahttp.AkkaHttpBackend -import sttp.apispec.asyncapi.Server -import sttp.apispec.asyncapi.circe.yaml._ -import sttp.tapir._ -import sttp.tapir.docs.asyncapi.AsyncAPIInterpreter -import sttp.tapir.json.circe._ -import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter -import sttp.ws.WebSocket - -import scala.concurrent.duration._ -import scala.concurrent.{Await, Future} - -object WebSocketAkkaServer extends App { - case class Response(hello: String) - - // The web socket endpoint: GET /ping. - // We need to provide both the type & media type for the requests, and responses. Here, the requests will be - // strings, and responses will be returned as json. - val wsEndpoint: PublicEndpoint[Unit, Unit, Flow[String, Response, Any], AkkaStreams with WebSockets] = - endpoint.get.in("ping").out(webSocketBody[String, CodecFormat.TextPlain, Response, CodecFormat.Json](AkkaStreams)) - - implicit val actorSystem: ActorSystem = ActorSystem() - import actorSystem.dispatcher - - // Implementation of the web socket: a flow which echoes incoming messages - val wsRoute: Route = - AkkaHttpServerInterpreter().toRoute( - wsEndpoint.serverLogicSuccess(_ => Future.successful(Flow.fromFunction((in: String) => Response(in)): Flow[String, Response, Any])) - ) - - // Documentation - val apiDocs = AsyncAPIInterpreter().toAsyncAPI(wsEndpoint, "JSON echo", "1.0", List("dev" -> Server("localhost:8080", "ws"))).toYaml - println(s"Paste into https://playground.asyncapi.io/ to see the docs for this endpoint:\n$apiDocs") - - // Starting the server - val bindAndCheck = Http() - .newServerAt("localhost", 8080) - .bindFlow(wsRoute) - .flatMap { _ => - // We could have interpreted wsEndpoint as a client, but here we are using sttp client directly - val backend: SttpBackend[Future, WebSockets] = AkkaHttpBackend.usingActorSystem(actorSystem) - // Client which interacts with the web socket - basicRequest - .response(asWebSocket { ws: WebSocket[Future] => - for { - _ <- ws.sendText("world") - _ <- ws.sendText("there") - r1 <- ws.receiveText() - _ = println(r1) - r2 <- ws.receiveText() - _ = println(r2) - _ <- ws.sendText("how are you") - r3 <- ws.receiveText() - _ = println(r3) - } yield () - }) - .get(uri"ws://localhost:8080/ping") - .send(backend) - } - - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) -} diff --git a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketHttp4sServer.scala index 1fff3bb737..f8b2c770fa 100644 --- a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketHttp4sServer.scala @@ -1,27 +1,27 @@ package sttp.tapir.examples.websocket import cats.effect.{ExitCode, IO, IOApp} -import io.circe.generic.auto._ -import fs2._ +import io.circe.generic.auto.* +import fs2.* import org.http4s.HttpRoutes import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.server.Router import org.http4s.server.websocket.WebSocketBuilder2 +import sttp.apispec.asyncapi.Server +import sttp.apispec.asyncapi.circe.yaml.* import sttp.capabilities.WebSockets import sttp.capabilities.fs2.Fs2Streams -import sttp.client3._ +import sttp.client3.* import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend -import sttp.apispec.asyncapi.Server -import sttp.apispec.asyncapi.circe.yaml._ -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.docs.asyncapi.AsyncAPIInterpreter -import sttp.tapir.generic.auto._ -import sttp.tapir.json.circe._ +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* import sttp.tapir.server.http4s.Http4sServerInterpreter import sttp.ws.WebSocket import scala.concurrent.ExecutionContext -import scala.concurrent.duration._ +import scala.concurrent.duration.* object WebSocketHttp4sServer extends IOApp { @@ -80,7 +80,7 @@ object WebSocketHttp4sServer extends IOApp { .use { backend => // Client which interacts with the web socket basicRequest - .response(asWebSocket { ws: WebSocket[IO] => + .response(asWebSocket { (ws: WebSocket[IO]) => for { _ <- ws.sendText("7 bytes") _ <- ws.sendText("7 bytes") diff --git a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketPekkoServer.scala index 6736ff941b..479186a228 100644 --- a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketPekkoServer.scala @@ -1,24 +1,24 @@ package sttp.tapir.examples.websocket +import io.circe.generic.auto.* import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Route import org.apache.pekko.stream.scaladsl.Flow -import io.circe.generic.auto._ -import sttp.tapir.generic.auto._ +import sttp.apispec.asyncapi.Server +import sttp.apispec.asyncapi.circe.yaml.* import sttp.capabilities.WebSockets import sttp.capabilities.pekko.PekkoStreams -import sttp.client3._ +import sttp.client3.* import sttp.client3.pekkohttp.PekkoHttpBackend -import sttp.apispec.asyncapi.Server -import sttp.apispec.asyncapi.circe.yaml._ -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.docs.asyncapi.AsyncAPIInterpreter -import sttp.tapir.json.circe._ +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter import sttp.ws.WebSocket -import scala.concurrent.duration._ +import scala.concurrent.duration.* import scala.concurrent.{Await, Future} object WebSocketPekkoServer extends App { @@ -47,12 +47,12 @@ object WebSocketPekkoServer extends App { val bindAndCheck = Http() .newServerAt("localhost", 8080) .bindFlow(wsRoute) - .flatMap { _ => + .flatMap { binding => // We could have interpreted wsEndpoint as a client, but here we are using sttp client directly val backend: SttpBackend[Future, WebSockets] = PekkoHttpBackend.usingActorSystem(actorSystem) // Client which interacts with the web socket basicRequest - .response(asWebSocket { ws: WebSocket[Future] => + .response(asWebSocket { (ws: WebSocket[Future]) => for { _ <- ws.sendText("world") _ <- ws.sendText("there") @@ -67,7 +67,8 @@ object WebSocketPekkoServer extends App { }) .get(uri"ws://localhost:8080/ping") .send(backend) + .map(_ => binding) } - Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute) + Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) } diff --git a/examples3/src/main/resources/logback.xml b/examples2/src/main/resources/logback.xml similarity index 100% rename from examples3/src/main/resources/logback.xml rename to examples2/src/main/resources/logback.xml diff --git a/examples/src/main/resources/webapp/img/logo.png b/examples2/src/main/resources/webapp/img/logo.png similarity index 100% rename from examples/src/main/resources/webapp/img/logo.png rename to examples2/src/main/resources/webapp/img/logo.png diff --git a/examples/src/main/resources/webapp/index.html b/examples2/src/main/resources/webapp/index.html similarity index 100% rename from examples/src/main/resources/webapp/index.html rename to examples2/src/main/resources/webapp/index.html diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldAkkaServer.scala b/examples2/src/main/scala/sttp/tapir/examples2/HelloWorldAkkaServer.scala similarity index 98% rename from examples/src/main/scala/sttp/tapir/examples/HelloWorldAkkaServer.scala rename to examples2/src/main/scala/sttp/tapir/examples2/HelloWorldAkkaServer.scala index 73ff6592c4..2f96ca1027 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldAkkaServer.scala +++ b/examples2/src/main/scala/sttp/tapir/examples2/HelloWorldAkkaServer.scala @@ -1,4 +1,4 @@ -package sttp.tapir.examples +package sttp.tapir.examples2 import akka.actor.ActorSystem import akka.http.scaladsl.Http diff --git a/examples/src/main/scala/sttp/tapir/examples/custom_types/SealedTraitWithDiscriminator.scala b/examples2/src/main/scala/sttp/tapir/examples2/custom_types/SealedTraitWithDiscriminator.scala similarity index 98% rename from examples/src/main/scala/sttp/tapir/examples/custom_types/SealedTraitWithDiscriminator.scala rename to examples2/src/main/scala/sttp/tapir/examples2/custom_types/SealedTraitWithDiscriminator.scala index d86f855569..075452d6af 100644 --- a/examples/src/main/scala/sttp/tapir/examples/custom_types/SealedTraitWithDiscriminator.scala +++ b/examples2/src/main/scala/sttp/tapir/examples2/custom_types/SealedTraitWithDiscriminator.scala @@ -1,4 +1,4 @@ -package sttp.tapir.examples.custom_types +package sttp.tapir.examples2.custom_types // Note that you'll need the extras.auto._ import, not the usual one import io.circe.generic.extras.auto._ diff --git a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicZio.scala b/examples2/src/main/scala/sttp/tapir/examples2/security/ServerSecurityLogicZio.scala similarity index 98% rename from examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicZio.scala rename to examples2/src/main/scala/sttp/tapir/examples2/security/ServerSecurityLogicZio.scala index 13a50f4580..f7b030f647 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicZio.scala +++ b/examples2/src/main/scala/sttp/tapir/examples2/security/ServerSecurityLogicZio.scala @@ -1,4 +1,4 @@ -package sttp.tapir.examples.security +package sttp.tapir.examples2.security import sttp.client3._ import sttp.client3.asynchttpclient.zio.AsyncHttpClientZioBackend diff --git a/examples/src/main/scala/sttp/tapir/examples/testing/SttpMockServerClientExample.scala b/examples2/src/main/scala/sttp/tapir/examples2/testing/SttpMockServerClientExample.scala similarity index 98% rename from examples/src/main/scala/sttp/tapir/examples/testing/SttpMockServerClientExample.scala rename to examples2/src/main/scala/sttp/tapir/examples2/testing/SttpMockServerClientExample.scala index 6560b4f548..678b641b27 100644 --- a/examples/src/main/scala/sttp/tapir/examples/testing/SttpMockServerClientExample.scala +++ b/examples2/src/main/scala/sttp/tapir/examples2/testing/SttpMockServerClientExample.scala @@ -1,4 +1,4 @@ -package sttp.tapir.examples.testing +package sttp.tapir.examples2.testing import io.circe.generic.auto._ import org.mockserver.integration.ClientAndServer.startClientAndServer diff --git a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketAkkaClient.scala b/examples2/src/main/scala/sttp/tapir/examples2/websocket/WebSocketAkkaClient.scala similarity index 97% rename from examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketAkkaClient.scala rename to examples2/src/main/scala/sttp/tapir/examples2/websocket/WebSocketAkkaClient.scala index 293f375f62..755c6cda12 100644 --- a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketAkkaClient.scala +++ b/examples2/src/main/scala/sttp/tapir/examples2/websocket/WebSocketAkkaClient.scala @@ -1,4 +1,4 @@ -package sttp.tapir.examples.websocket +package sttp.tapir.examples2.websocket import akka.actor.ActorSystem import akka.stream.scaladsl.{Flow, Sink, Source}