diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b5a07c93cd..9c35d8ca65 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -74,7 +74,7 @@ jobs:
- name: Compile
run: sbt $SBT_JAVA_OPTS -v "compileScoped ${{ matrix.scala-version }} ${{ matrix.target-platform }}"
- name: Compile documentation
- if: matrix.target-platform == 'JVM' && matrix.java == '11'
+ if: matrix.target-platform == 'JVM' && matrix.java == '21'
run: sbt $SBT_JAVA_OPTS -v compileDocumentation
- name: Test
if: matrix.target-platform == 'JVM' && matrix.scala-version == '2.12'
@@ -131,11 +131,11 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0 # checkout tags so that dynver works properly (we need the version for MiMa)
- - name: Set up JDK 11
+ - name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
- java-version: 11
+ java-version: 21
cache: 'sbt'
- name: Check MiMa
run: sbt $SBT_JAVA_OPTS -v mimaReportBinaryIssues
diff --git a/build.sbt b/build.sbt
index 3e7c5c7aaf..b10cd7c8ce 100644
--- a/build.sbt
+++ b/build.sbt
@@ -18,16 +18,13 @@ val scala2_12 = "2.12.19"
val scala2_13 = "2.13.14"
val scala3 = "3.3.3"
-// The `idea.managed` property is set automatically by IntelliJ when it runs sbt for build or import
-val ideaManaged = System.getProperty("idea.managed", "false").toBoolean
-val ideScalaVersion = if (ideaManaged) scala2_13 else scala3
-
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(scala3)
-val documentationScalaVersion = scala2_13
+val examplesScalaVersion = scala3
+val documentationScalaVersion = scala3
+val ideScalaVersion = scala3
lazy val clientTestServerPort = settingKey[Int]("Port to run the client interpreter test server on")
lazy val startClientTestServer = taskKey[Unit]("Start a http server used by client interpreter tests")
@@ -78,8 +75,12 @@ val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq(
bspEnabled := !ideSkipProject.value,
// slow down for CI
Test / parallelExecution := false,
- // remove false alarms about unused implicit definitions in macros
- scalacOptions ++= Seq("-Ywarn-macros:after"),
+ scalacOptions ++= {
+ CrossVersion.partialVersion(scalaVersion.value) match {
+ case Some((2, _)) => Seq("-Ywarn-macros:after") // remove false alarms about unused implicit definitions in macros
+ case _ => Seq("-Xmax-inlines", "64")
+ }
+ },
evictionErrorLevel := Level.Info
)
@@ -240,7 +241,6 @@ lazy val rawAllAggregates = core.projectRefs ++
play29Client.projectRefs ++
tests.projectRefs ++
perfTests.projectRefs ++
- examples2.projectRefs ++
examples.projectRefs ++
documentation.projectRefs ++
openapiCodegenCore.projectRefs ++
@@ -250,7 +250,7 @@ lazy val rawAllAggregates = core.projectRefs ++
derevo.projectRefs ++
awsCdk.projectRefs
-lazy val loomProjects: Seq[String] = Seq(nettyServerSync, nimaServer, examples).flatMap(_.projectRefs).flatMap(projectId)
+lazy val loomProjects: Seq[String] = Seq(nettyServerSync, nimaServer, examples, documentation).flatMap(_.projectRefs).flatMap(projectId)
def projectId(projectRef: ProjectReference): Option[String] =
projectRef match {
@@ -797,7 +797,7 @@ lazy val json4s: ProjectMatrix = (projectMatrix in file("json/json4s"))
scalaTest.value % Test
)
)
- .jvmPlatform(scalaVersions = scala2Versions)
+ .jvmPlatform(scalaVersions = scala2And3Versions)
.dependsOn(core)
lazy val playJson: ProjectMatrix = (projectMatrix in file("json/playjson"))
@@ -2023,68 +2023,6 @@ lazy val openapiCodegenCli: ProjectMatrix = (projectMatrix in file("openapi-code
// other
-lazy val examples2: ProjectMatrix = (projectMatrix in file("examples2"))
- .settings(commonJvmSettings)
- .settings(
- name := "tapir-examples2",
- libraryDependencies ++= Seq(
- "dev.zio" %% "zio-interop-cats" % Versions.zioInteropCats,
- "org.typelevel" %% "cats-effect" % Versions.catsEffect,
- "org.http4s" %% "http4s-dsl" % Versions.http4s,
- "org.http4s" %% "http4s-circe" % Versions.http4s,
- "org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer,
- "com.softwaremill.sttp.client3" %% "akka-http-backend" % 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.softwaremill.sttp.apispec" %% "asyncapi-circe-yaml" % Versions.sttpApispec,
- "com.github.jwt-scala" %% "jwt-circe" % Versions.jwtScala,
- "org.mock-server" % "mockserver-netty" % Versions.mockServer,
- "io.circe" %% "circe-generic-extras" % Versions.circeGenericExtras,
- "io.opentelemetry" % "opentelemetry-sdk" % Versions.openTelemetry,
- "io.opentelemetry" % "opentelemetry-sdk-metrics" % Versions.openTelemetry,
- "io.opentelemetry" % "opentelemetry-exporter-otlp" % Versions.openTelemetry,
- scalaTest.value,
- logback
- ),
- publishArtifact := false,
- Compile / run / fork := true
- )
- .jvmPlatform(scalaVersions = List(scala2_13))
- .dependsOn(
- akkaHttpServer,
- pekkoHttpServer,
- armeriaServer,
- jdkhttpServer,
- http4sServer,
- http4sServerZio,
- http4sClient,
- sttpClient,
- openapiDocs,
- asyncapiDocs,
- circeJson,
- swaggerUiBundle,
- redocBundle,
- zioHttpServer,
- nettyServer,
- nettyServerCats,
- nettyServerZio,
- sttpStubServer,
- playJson,
- prometheusMetrics,
- opentelemetryMetrics,
- datadogMetrics,
- zioMetrics,
- sttpMockServer,
- zioJson,
- vertxServer,
- vertxServerCats,
- vertxServerZio,
- finatraServer,
- protobuf
- )
-
lazy val examples: ProjectMatrix = (projectMatrix in file("examples"))
.settings(commonJvmSettings)
.settings(
@@ -2100,6 +2038,7 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples"))
"org.http4s" %% "http4s-dsl" % Versions.http4s,
"org.http4s" %% "http4s-circe" % Versions.http4s,
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer,
+ "org.mock-server" % "mockserver-netty" % Versions.mockServer,
"io.opentelemetry" % "opentelemetry-sdk" % Versions.openTelemetry,
"io.opentelemetry" % "opentelemetry-sdk-metrics" % Versions.openTelemetry,
"io.opentelemetry" % "opentelemetry-exporter-otlp" % Versions.openTelemetry,
@@ -2109,32 +2048,33 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples"))
publishArtifact := false,
Compile / run / fork := true
)
- .jvmPlatform(scalaVersions = examplesScalaVersions)
+ .jvmPlatform(scalaVersions = List(examplesScalaVersion))
.dependsOn(
- datadogMetrics,
- prometheusMetrics,
- opentelemetryMetrics,
- zioMetrics,
+ armeriaServer,
+ asyncapiDocs,
circeJson,
+ datadogMetrics,
+ http4sClient,
http4sServer,
- pekkoHttpServer,
- armeriaServer,
- nettyServer,
+ http4sServerZio,
+ iron,
jdkhttpServer,
+ nettyServer,
nettyServerCats,
- http4sClient,
+ nettyServerSync,
+ nettyServerZio,
+ opentelemetryMetrics,
+ pekkoHttpServer,
picklerJson,
+ prometheusMetrics,
sttpClient,
+ sttpMockServer,
+ sttpStubServer,
swaggerUiBundle,
- http4sServerZio,
- nettyServerSync,
- nettyServerZio,
+ redocBundle,
zioHttpServer,
zioJson,
- redocBundle,
- sttpStubServer,
- asyncapiDocs,
- iron
+ zioMetrics
)
//TODO this should be invoked by compilation process, see #https://github.com/scalameta/mdoc/issues/355
@@ -2171,31 +2111,36 @@ lazy val documentation: ProjectMatrix = (projectMatrix in file("generated-doc"))
)
.jvmPlatform(scalaVersions = List(documentationScalaVersion))
.dependsOn(
- core % "compile->test",
- testing,
- akkaHttpServer,
- pekkoHttpServer,
armeriaServer,
armeriaServerCats,
armeriaServerZio,
- jdkhttpServer,
+ asyncapiDocs,
circeJson,
+ core % "compile->test",
+ datadogMetrics,
enumeratum,
- finatraServer,
- finatraServerCats,
+ http4sClient,
+ http4sServerZio,
+ jdkhttpServer,
jsoniterScala,
- asyncapiDocs,
- openapiDocs,
json4s,
+ nettyServer,
+ nettyServerCats,
+ nettyServerSync,
+ openapiDocs,
+ opentelemetryMetrics,
+ pekkoHttpServer,
+ picklerJson,
+ playClient,
playJson,
playServer,
+ prometheusMetrics,
sprayJson,
- http4sClient,
- http4sServerZio,
- nettyServerCats,
sttpClient,
- playClient,
+ sttpMockServer,
sttpStubServer,
+ swaggerUiBundle,
+ testing,
tethysJson,
uPickleJson,
vertxServer,
@@ -2203,13 +2148,6 @@ lazy val documentation: ProjectMatrix = (projectMatrix in file("generated-doc"))
vertxServerZio,
zio,
zioHttpServer,
- derevo,
zioJson,
- prometheusMetrics,
- opentelemetryMetrics,
- datadogMetrics,
- zioMetrics,
- sttpMockServer,
- nettyServer,
- swaggerUiBundle
+ zioMetrics
)
diff --git a/core/src/main/scala-3/sttp/tapir/macros/CodecMacros.scala b/core/src/main/scala-3/sttp/tapir/macros/CodecMacros.scala
index 5346de1105..48590f719f 100644
--- a/core/src/main/scala-3/sttp/tapir/macros/CodecMacros.scala
+++ b/core/src/main/scala-3/sttp/tapir/macros/CodecMacros.scala
@@ -3,12 +3,8 @@ package sttp.tapir.macros
import sttp.tapir.CodecFormat.TextPlain
import sttp.tapir.{Codec, SchemaAnnotations, Validator}
import sttp.tapir.internal.CodecValueClassMacro
-import sttp.tapir.Mapping
-import sttp.tapir.DecodeResult
-import sttp.tapir.DecodeResult.Value
-import sttp.tapir.Schema
-trait CodecMacros {
+trait CodecMacros:
/** Creates a codec for an enumeration, where the validator is derived using [[sttp.tapir.Validator.derivedEnumeration]]. This requires
* that all subtypes of the sealed hierarchy `T` must be `object`s.
@@ -64,4 +60,3 @@ trait CodecMacros {
/** Creates a codec for value class based on codecs defined in `Codec` companion */
implicit inline def derivedValueClass[T <: AnyVal]: Codec[String, T, TextPlain] = CodecValueClassMacro.derivedValueClass[T]
-}
diff --git a/doc/client/play.md b/doc/client/play.md
index adc409ea81..c33987fcb2 100644
--- a/doc/client/play.md
+++ b/doc/client/play.md
@@ -51,7 +51,7 @@ After providing the input parameters, the two following are returned:
Example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.client.play.PlayClientInterpreter
import sttp.capabilities.pekko.PekkoStreams
@@ -60,7 +60,7 @@ import scala.concurrent.Future
import play.api.libs.ws.StandaloneWSClient
-def example[I, E, O, R >: PekkoStreams](implicit wsClient: StandaloneWSClient) {
+def example[I, E, O, R >: PekkoStreams](implicit wsClient: StandaloneWSClient): Unit =
val e: PublicEndpoint[I, E, O, R] = ???
val inputArgs: I = ???
@@ -71,7 +71,6 @@ def example[I, E, O, R >: PekkoStreams](implicit wsClient: StandaloneWSClient) {
val result: Future[Either[E, O]] = req
.execute()
.map(responseParser)
-}
```
## Limitations
diff --git a/doc/client/sttp.md b/doc/client/sttp.md
index 268f2d8237..7ebdc94967 100644
--- a/doc/client/sttp.md
+++ b/doc/client/sttp.md
@@ -66,10 +66,10 @@ convert sttp's `WebSocket` instance into a pipe. This logic is looked up via the
The required imports are as follows:
```scala
-import sttp.tapir.client.sttp.ws.pekkohttp._ // for pekko-streams
-import sttp.tapir.client.sttp.ws.akkahttp._ // for akka-streams
-import sttp.tapir.client.sttp.ws.fs2._ // for fs2
-import sttp.tapir.client.sttp.ws.zio._ // for zio
+import sttp.tapir.client.sttp.ws.pekkohttp.* // for pekko-streams
+import sttp.tapir.client.sttp.ws.akkahttp.* // for akka-streams
+import sttp.tapir.client.sttp.ws.fs2.* // for fs2
+import sttp.tapir.client.sttp.ws.zio.* // for zio
```
No additional dependencies are needed, as both of the above implementations are included in the main interpreter,
@@ -85,9 +85,9 @@ If you'd like to skip that step, e.g. when testing redirects, it's possible to o
description, for example:
```scala :compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.client.sttp.SttpClientInterpreter
-import sttp.client3._
+import sttp.client3.*
SttpClientInterpreter()
.toRequest(endpoint.get.in("hello").in(query[String]("name")), Some(uri"http://localhost:8080"))
diff --git a/doc/docs/asyncapi.md b/doc/docs/asyncapi.md
index 395792f90e..d6bad3c745 100644
--- a/doc/docs/asyncapi.md
+++ b/doc/docs/asyncapi.md
@@ -15,16 +15,16 @@ object:
```scala mdoc:silent
import sttp.apispec.asyncapi.AsyncAPI
-import sttp.capabilities.akka.AkkaStreams
-import sttp.tapir._
+import sttp.capabilities.pekko.PekkoStreams
+import sttp.tapir.*
import sttp.tapir.docs.asyncapi.AsyncAPIInterpreter
-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 io.circe.generic.auto.*
case class Response(msg: String, count: Int)
val echoWS = endpoint.out(
- webSocketBody[String, CodecFormat.TextPlain, Response, CodecFormat.Json](AkkaStreams))
+ webSocketBody[String, CodecFormat.TextPlain, Response, CodecFormat.Json](PekkoStreams))
val docs: AsyncAPI = AsyncAPIInterpreter().toAsyncAPI(echoWS, "Echo web socket", "1.0")
```
@@ -56,7 +56,7 @@ Multiple endpoints can be converted to an `AsyncAPI` instance by calling the met
The asyncapi case classes can then be serialised, either to JSON or YAML using [Circe](https://circe.github.io/circe/):
```scala mdoc:silent
-import sttp.apispec.asyncapi.circe.yaml._
+import sttp.apispec.asyncapi.circe.yaml.*
println(docs.toYaml)
```
@@ -84,7 +84,7 @@ Specification extensions can be added by first importing an extension method, an
method which manipulates the appropriate attribute on the schema, endpoint or endpoint input/output:
```scala mdoc:silent
-import sttp.tapir.docs.apispec.DocsExtensionAttribute._
+import sttp.tapir.docs.apispec.DocsExtensionAttribute.*
endpoint
.post
@@ -97,4 +97,4 @@ look at **OpenAPI Specification Extensions** section of [documentation](../docs/
## Exposing AsyncAPI documentation
-AsyncAPI documentation can be exposed through the [AsyncAPI playground](https://playground.asyncapi.io).
\ No newline at end of file
+AsyncAPI documentation can be exposed through the [AsyncAPI playground](https://playground.asyncapi.io).
diff --git a/doc/docs/json-schema.md b/doc/docs/json-schema.md
index 56b350a9d5..9b5f2603bb 100644
--- a/doc/docs/json-schema.md
+++ b/doc/docs/json-schema.md
@@ -10,22 +10,22 @@ Schema generation can now be performed like in the following example:
```scala mdoc:compile-only
import sttp.apispec.{Schema => ASchema}
-import sttp.tapir._
-import sttp.tapir.docs.apispec.schema._
-import sttp.tapir.generic.auto._
+import sttp.tapir.*
+import sttp.tapir.docs.apispec.schema.*
+import sttp.tapir.generic.auto.*
- object Childhood {
- case class Child(age: Int, height: Option[Int])
- }
- case class Parent(innerChildField: Child, childDetails: Childhood.Child)
- case class Child(childName: String) // to illustrate unique name generation
- val tSchema = implicitly[Schema[Parent]]
-
- val jsonSchema: ASchema = TapirSchemaToJsonSchema(
- tSchema,
- markOptionsAsNullable = true,
- metaSchema = MetaSchemaDraft04 // default
- // schemaName = sttp.atpir.docs.apispec.defaultSchemaName // default
+object Childhood {
+ case class Child(age: Int, height: Option[Int])
+}
+case class Parent(innerChildField: Child, childDetails: Childhood.Child)
+case class Child(childName: String) // to illustrate unique name generation
+val tSchema = implicitly[Schema[Parent]]
+
+val jsonSchema: ASchema = TapirSchemaToJsonSchema(
+ tSchema,
+ markOptionsAsNullable = true,
+ metaSchema = MetaSchemaDraft04 // default
+ // schemaName = sttp.atpir.docs.apispec.defaultSchemaName // default
)
```
@@ -42,28 +42,28 @@ you will get a codec for `sttp.apispec.Schema`:
```scala mdoc:compile-only
import io.circe.Printer
-import io.circe.syntax._
-import sttp.apispec.circe._
-import sttp.apispec.{Schema => ASchema, SchemaType => ASchemaType}
-import sttp.tapir._
-import sttp.tapir.docs.apispec.schema._
-import sttp.tapir.generic.auto._
+import io.circe.syntax.*
+import sttp.apispec.circe.*
+import sttp.apispec.{Schema => ASchema}
+import sttp.tapir.*
+import sttp.tapir.docs.apispec.schema.*
+import sttp.tapir.generic.auto.*
import sttp.tapir.Schema.annotations.title
- object Childhood {
- @title("my child") case class Child(age: Int, height: Option[Int])
- }
- case class Parent(innerChildField: Child, childDetails: Childhood.Child)
- case class Child(childName: String)
- val tSchema = implicitly[Schema[Parent]]
-
- val jsonSchema: ASchema = TapirSchemaToJsonSchema(
- tSchema,
- markOptionsAsNullable = true)
-
- // JSON serialization
- val schemaAsJson = jsonSchema.asJson
- val schemaStr: String = Printer.spaces2.print(schemaAsJson.deepDropNullValues)
+object Childhood {
+ @title("my child") case class Child(age: Int, height: Option[Int])
+}
+case class Parent(innerChildField: Child, childDetails: Childhood.Child)
+case class Child(childName: String)
+val tSchema = implicitly[Schema[Parent]]
+
+val jsonSchema: ASchema = TapirSchemaToJsonSchema(
+ tSchema,
+ markOptionsAsNullable = true)
+
+// JSON serialization
+val schemaAsJson = jsonSchema.asJson
+val schemaStr: String = Printer.spaces2.print(schemaAsJson.deepDropNullValues)
```
The title annotation of the object will be by default the name of the case class. You can customize it with `@title` annotation.
diff --git a/doc/docs/openapi.md b/doc/docs/openapi.md
index dd7a5ae9aa..5ccbcf6189 100644
--- a/doc/docs/openapi.md
+++ b/doc/docs/openapi.md
@@ -22,7 +22,7 @@ with the endpoints for which the documentation is generated, will need in turn t
interpreter. For example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.swagger.bundle.SwaggerInterpreter
import sttp.tapir.server.netty.{NettyFutureServerInterpreter, FutureRoute}
@@ -77,7 +77,7 @@ object:
```scala mdoc:silent
import sttp.apispec.openapi.OpenAPI
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter
val booksListing = endpoint.in(path[String]("bookId"))
@@ -115,7 +115,7 @@ OpenAPIDocsInterpreter().toOpenAPI(List(addBook, booksListing, booksListingByGen
The openapi case classes can then be serialised to YAML using [Circe](https://circe.github.io/circe/):
```scala mdoc:silent
-import sttp.apispec.openapi.circe.yaml._
+import sttp.apispec.openapi.circe.yaml.*
println(docs.toYaml)
```
@@ -124,8 +124,8 @@ Or to JSON:
```scala mdoc:silent
import io.circe.Printer
-import io.circe.syntax._
-import sttp.apispec.openapi.circe._
+import io.circe.syntax.*
+import sttp.apispec.openapi.circe.*
println(Printer.spaces2.print(docs.asJson))
```
@@ -144,8 +144,8 @@ Firstly add dependencies:
and generate the documentation by importing valid extension methods and explicitly specifying the "3.0.3" version in the OpenAPI model:
```scala mdoc:compile-only
import sttp.apispec.openapi.OpenAPI
-import sttp.apispec.openapi.circe.yaml._ // for `toYaml` extension method
-import sttp.tapir._
+import sttp.apispec.openapi.circe.yaml.* // for `toYaml` extension method
+import sttp.tapir.*
import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter
case class Book(id: Option[Long], title: Option[String])
@@ -178,8 +178,8 @@ or `tapir-redoc`:
Then, you'll need to pass the server endpoints to your server interpreter. For example, using akka-http:
```scala mdoc:compile-only
-import sttp.apispec.openapi.circe.yaml._
-import sttp.tapir._
+import sttp.apispec.openapi.circe.yaml.*
+import sttp.tapir.*
import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter
import sttp.tapir.server.netty.{NettyFutureServerInterpreter, FutureRoute}
import sttp.tapir.swagger.SwaggerUI
@@ -227,7 +227,7 @@ that is they will form a single security requirement, with multiple schemes, e.g
```scala mdoc:compile-only
import sttp.model.headers.WWWAuthenticateChallenge
-import sttp.tapir._
+import sttp.tapir.*
val multiAuthEndpoint =
endpoint.post
@@ -251,7 +251,7 @@ can be done in the security logic, server logic, or by mapping the inputs using
```scala mdoc:compile-only
import sttp.model.headers.WWWAuthenticateChallenge
-import sttp.tapir._
+import sttp.tapir.*
val alternativeAuthEndpoint = endpoint.securityIn(
// auth.apiKey(...).and(auth.apiKey(..)) will map the request headers to a tuple (Option[String], Option[String])
@@ -285,16 +285,16 @@ Specification extensions can be added by first importing an extension method, an
method which manipulates the appropriate attribute on the schema, endpoint or endpoint input/output:
```scala mdoc:compile-only
-import sttp.apispec.openapi._
-import sttp.apispec.openapi.circe._
-import sttp.apispec.openapi.circe.yaml._
-import sttp.tapir._
-import sttp.tapir.json.circe._
-import sttp.tapir.generic.auto._
-import io.circe.generic.auto._
+import sttp.apispec.openapi.*
+import sttp.apispec.openapi.circe.*
+import sttp.apispec.openapi.circe.yaml.*
+import sttp.tapir.*
+import sttp.tapir.json.circe.*
+import sttp.tapir.generic.auto.*
+import io.circe.generic.auto.*
import sttp.tapir.docs.apispec.DocsExtension
-import sttp.tapir.docs.apispec.DocsExtensionAttribute._
+import sttp.tapir.docs.apispec.DocsExtensionAttribute.*
import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter
case class FruitAmount(fruit: String, amount: Int)
@@ -333,7 +333,7 @@ If you are using `tapir-swagger-ui` you need to set `withShowExtensions` option
It's possible to hide an input/output from the OpenAPI description using following syntax:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
val acceptHeader: EndpointInput[String] = header[String]("Accept").schema(_.hidden(true))
```
diff --git a/doc/endpoint/basics.md b/doc/endpoint/basics.md
index 818b37c7f9..54be500622 100644
--- a/doc/endpoint/basics.md
+++ b/doc/endpoint/basics.md
@@ -17,7 +17,7 @@ Input/output parameters (`A`, `I`, `E` and `O`) can be:
Hence, an empty, initial endpoint, with no inputs and no outputs, from which all other endpoints are derived has the type:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
val endpoint: Endpoint[Unit, Unit, Unit, Unit, Any] = ???
```
@@ -25,7 +25,7 @@ val endpoint: Endpoint[Unit, Unit, Unit, Unit, Any] = ???
For endpoints which have no security inputs, a type alias is provided which fixes `A` to `Unit`:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
type PublicEndpoint[I, E, O, -R] = Endpoint[Unit, I, E, O, R]
```
@@ -40,7 +40,7 @@ case class User()
```
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
val userEndpoint: PublicEndpoint[(UUID, Int), String, User, Any] = ???
```
diff --git a/doc/endpoint/contenttype.md b/doc/endpoint/contenttype.md
index d1c120f109..541d1dea8f 100644
--- a/doc/endpoint/contenttype.md
+++ b/doc/endpoint/contenttype.md
@@ -24,13 +24,13 @@ On the client side, the appropriate mapping will be chosen basing on the `Conten
For example:
```scala
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.Codec.{JsonCodec, XmlCodec}
import sttp.model.StatusCode
case class Entity(name: String)
-implicit val jsonCodecForOrganization: JsonCodec[Entity] = ???
-implicit val xmlCodecForOrganization: XmlCodec[Entity] = ???
+given JsonCodec[Entity] = ???
+given XmlCodec[Entity] = ???
endpoint.out(
oneOf(
diff --git a/doc/endpoint/customtypes.md b/doc/endpoint/customtypes.md
index 6f8e6e6dc8..5e4a71ce11 100644
--- a/doc/endpoint/customtypes.md
+++ b/doc/endpoint/customtypes.md
@@ -46,29 +46,26 @@ need to provide two mappings:
For example, to support a custom id type:
```scala mdoc:silent
-import scala.util._
+import scala.util.*
-class MyId private (id: String) {
+class MyId private (id: String):
override def toString(): String = id
-}
-object MyId {
- def parse(id: String): Try[MyId] = {
- Success(new MyId(id))
- }
-}
+
+object MyId:
+ def parse(id: String): Try[MyId] = Success(new MyId(id))
```
```scala mdoc:silent
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.CodecFormat.TextPlain
-def decode(s: String): DecodeResult[MyId] = MyId.parse(s) match {
+def decode(s: String): DecodeResult[MyId] = MyId.parse(s) match
case Success(v) => DecodeResult.Value(v)
case Failure(f) => DecodeResult.Error(s, f)
-}
+
def encode(id: MyId): String = id.toString
-implicit val myIdCodec: Codec[String, MyId, TextPlain] =
+given Codec[String, MyId, TextPlain] =
Codec.string.mapDecode(decode)(encode)
```
@@ -77,7 +74,7 @@ Or, using the type alias for codecs in the `TextPlain` format and `String` as th
```scala mdoc:silent:nest
import sttp.tapir.Codec.PlainCodec
-implicit val myIdCodec: PlainCodec[MyId] = Codec.string.mapDecode(decode)(encode)
+given PlainCodec[MyId] = Codec.string.mapDecode(decode)(encode)
```
```{note}
diff --git a/doc/endpoint/enumerations.md b/doc/endpoint/enumerations.md
index 06bf3929a4..f07f8c6095 100644
--- a/doc/endpoint/enumerations.md
+++ b/doc/endpoint/enumerations.md
@@ -30,15 +30,14 @@ assumes that the low-level representation of the enumeration is a string. Encodi
decoding performs a case-insensitive search through the enumeration's values. For example:
```scala
-import sttp.tapir._
+import sttp.tapir.*
-object Features extends Enumeration {
+object Features extends Enumeration:
type Feature = Value
val A: Feature = Value("a")
val B: Feature = Value("b")
val C: Feature = Value("c")
-}
query[Features.Feature]("feature")
```
@@ -78,17 +77,16 @@ would be considered by the compiler.
For example:
```scala mdoc:silent:reset-object
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.Codec.PlainCodec
sealed trait Feature
-object Feature {
+object Feature:
case object A extends Feature
case object B extends Feature
case object C extends Feature
-}
-implicit val featureCodec: PlainCodec[Feature] =
+given PlainCodec[Feature] =
Codec.derivedEnumeration[String, Feature].defaultStringBased
query[Feature]("feature")
@@ -99,14 +97,14 @@ default `Enumeration` codec (using `.toString`). Such a codec can be similarly c
and `decode` functions as parameters to the value returned to `derivedEnumeration`:
```scala mdoc:silent:reset-object
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.Codec.PlainCodec
sealed trait Color
case object Blue extends Color
case object Red extends Color
-implicit val colorCodec: PlainCodec[Color] = {
+given PlainCodec[Color] =
Codec.derivedEnumeration[String, Color](
(_: String) match {
case "red" => Some(Red)
@@ -115,7 +113,6 @@ implicit val colorCodec: PlainCodec[Color] = {
},
_.toString.toLowerCase
)
-}
```
### Creating an enum codec by hand
@@ -131,16 +128,15 @@ If an input/output contains multiple enumeration values, delimited e.g. using a
type is a simple wrapper for a list of `T`-values. For example, if the query parameter is required:
```scala mdoc:silent
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.model.CommaSeparated
-object Features extends Enumeration {
+object Features extends Enumeration:
type Feature = Value
val A: Feature = Value("a")
val B: Feature = Value("b")
val C: Feature = Value("c")
-}
query[CommaSeparated[Features.Feature]]("features")
```
@@ -172,26 +168,25 @@ assumes that the low-level representation of the enumeration is a string. Encodi
represent the enumeration's values in the documentation). For example, to use an enum as part of a `jsonBody`, using
the circe library for JSON parsing/serialisation, and automatic schema derivation for case classes:
-```scala mdoc:silent:reset-object
-import io.circe._
-import io.circe.generic.auto._
-import sttp.tapir._
-import sttp.tapir.json.circe._
-import sttp.tapir.generic.auto._
+```scala
+import io.circe.*
+import io.circe.generic.auto.*
+import sttp.tapir.*
+import sttp.tapir.json.circe.*
+import sttp.tapir.generic.auto.*
-object Features extends Enumeration {
+object Features extends Enumeration:
type Feature = Value
val A: Feature = Value("a")
val B: Feature = Value("b")
val C: Feature = Value("c")
-}
case class Body(someField: String, feature: Features.Feature)
-// these need to be provided so that circe knows how to encode/decode enumerations
-implicit val enumDecoder: Decoder[Features.Feature] = Decoder.decodeEnumeration(Features)
-implicit val enumEncoder: Encoder[Features.Feature] = Encoder.encodeEnumeration(Features)
+// these need to be provided so that circe knows how to encode/decode enumerations - will work only in Scala2!
+given Decoder[Features.Feature] = Decoder.decodeEnumeration(Features)
+given Encoder[Features.Feature] = Encoder.encodeEnumeration(Features)
// the schema for the body is automatically-derived, using the default schema for
// enumerations (Schema.derivedEnumerationValue)
@@ -203,17 +198,16 @@ enumeration is an integer), using `Schema.derivedEnumerationValueCustomise.apply
to provide the schema an implicit/given value:
```scala mdoc:silent:reset-object
-import sttp.tapir._
+import sttp.tapir.*
-object Features extends Enumeration {
+object Features extends Enumeration:
type Feature = Value
val A: Feature = Value("a")
val B: Feature = Value("b")
val C: Feature = Value("c")
-}
-implicit val customFeatureSchema: Schema[Features.Feature] =
+given Schema[Features.Feature] =
Schema.derivedEnumerationValueCustomise[Features.Feature](
encode = Some {
case Features.A => 0
@@ -239,28 +233,26 @@ need to be created using `.derivedEnumeration`, instead of the more general `.de
For example:
```scala mdoc:silent:reset-object
-import sttp.tapir._
+import sttp.tapir.*
sealed trait Feature
-object Feature {
+object Feature:
case object A extends Feature
case object B extends Feature
case object C extends Feature
-}
-implicit val featureSchema: Schema[Feature] =
+given Schema[Feature] =
Schema.derivedEnumeration[Feature].defaultStringBased
```
Similarly, using Scala 3's enums:
```scala
-enum ColorEnum {
+enum ColorEnum:
case Green extends ColorEnum
case Pink extends ColorEnum
-}
-given Schema[ColorEnum] = Schema.derivedEnumeration.defaultStringBased
+given Schema.derivedEnumeration.defaultStringBased
```
### Scala 3 string-based constant union types to enum
diff --git a/doc/endpoint/forms.md b/doc/endpoint/forms.md
index 5d02d1c854..e7b855c995 100644
--- a/doc/endpoint/forms.md
+++ b/doc/endpoint/forms.md
@@ -6,7 +6,7 @@ An URL-encoded form input/output can be specified in two ways. First, it is poss
`Seq[(String, String)]`, or `Map[String, String]` (which is more convenient if fields can't have multiple values):
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
formBody[Seq[(String, String)]]: EndpointIO.Body[String, Seq[(String, String)]]
formBody[Map[String, String]]: EndpointIO.Body[String, Map[String, String]]
@@ -16,8 +16,8 @@ Second, form data can be mapped to a case class. The codec for the case class is
compile-time. The fields of the case class should have types, for which there is a plain text codec. For example:
```scala mdoc:compile-only
-import sttp.tapir._
-import sttp.tapir.generic.auto._
+import sttp.tapir.*
+import sttp.tapir.generic.auto.*
case class RegistrationForm(name: String, age: Int, news: Boolean, city: Option[String])
@@ -33,7 +33,7 @@ Similarly as above, multipart form input/outputs can be specified in two ways. T
use:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.model.Part
multipartBody: EndpointIO.Body[Seq[RawPart], Seq[Part[Array[Byte]]]]
@@ -56,10 +56,10 @@ Additionally, the case class to which the multipart body is mapped can contain b
For example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.model.Part
import java.io.File
-import sttp.tapir.generic.auto._
+import sttp.tapir.generic.auto.*
case class RegistrationForm(userData: User, photo: Part[File], news: Boolean)
case class User(email: String)
@@ -72,10 +72,10 @@ If there can be none or multiple parts for the same name, the fields can be wrap
or any other container `C` for which exists a codec `List[T] => C[T]`
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.model.Part
import java.io.File
-import sttp.tapir.generic.auto._
+import sttp.tapir.generic.auto.*
case class RegistrationForm(userData: Option[User], photos: List[Part[File]], news: Option[Part[Boolean]])
case class User(email: String)
diff --git a/doc/endpoint/integrations.md b/doc/endpoint/integrations.md
index 504d12a692..df96382423 100644
--- a/doc/endpoint/integrations.md
+++ b/doc/endpoint/integrations.md
@@ -15,8 +15,8 @@ datatypes as well as additional syntax:
"com.softwaremill.sttp.tapir" %% "tapir-cats" % "@VERSION@"
```
-- `import sttp.tapir.integ.cats.codec._` - brings schema, validator and codec instances
-- `import sttp.tapir.integ.cats.syntax._` - brings additional syntax for `tapir` types
+- `import sttp.tapir.integ.cats.codec.*` - brings schema, validator and codec instances
+- `import sttp.tapir.integ.cats.syntax.*` - brings additional syntax for `tapir` types
Additionally, the `tapir-cats-effect` module contains an implementation of the `CatsMonadError` class, providing a bridge
between the sttp-internal `MonadError` and the cats-effect `Sync` typeclass:
@@ -35,7 +35,7 @@ validators for `T Refined P` as long as a codec for `T` already exists:
```
You'll need to extend the `sttp.tapir.codec.refined.TapirCodecRefined`
-trait or `import sttp.tapir.codec.refined._` to bring the implicit values into scope.
+trait or `import sttp.tapir.codec.refined.*` to bring the implicit values into scope.
The refined codecs contain a validator which wrap/unwrap the value from/to its refined equivalent.
@@ -58,7 +58,7 @@ validators for `T :| P` as long as a codec for `T` already exists:
The module is only available for Scala 3 since iron is not designed to work with Scala 2.
You'll need to extend the `sttp.tapir.codec.refined.TapirCodecIron`
-trait or `import sttp.tapir.codec.iron._` to bring the implicit values into scope.
+trait or `import sttp.tapir.codec.iron.*` to bring the implicit values into scope.
The iron codecs contain a validator which apply the constraint to validated value.
@@ -86,11 +86,12 @@ 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")
-)
+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:
@@ -147,7 +148,7 @@ enumerations. To use, add the following dependency:
"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % "@VERSION@"
```
-Then, `import sttp.tapir.codec.enumeratum._`, or extends the `sttp.tapir.codec.enumeratum.TapirCodecEnumeratum` trait.
+Then, `import sttp.tapir.codec.enumeratum.*`, or extends the `sttp.tapir.codec.enumeratum.TapirCodecEnumeratum` trait.
This will bring into scope implicit values for values extending `*EnumEntry`.
@@ -160,7 +161,7 @@ schemas for types with a `@newtype` and `@newsubtype` annotations as long as a c
"com.softwaremill.sttp.tapir" %% "tapir-newtype" % "@VERSION@"
```
-Then, `import sttp.tapir.codec.newtype._`, or extend the `sttp.tapir.codec.newtype.TapirCodecNewType` trait to bring the implicit values into scope.
+Then, `import sttp.tapir.codec.newtype.*`, or extend the `sttp.tapir.codec.newtype.TapirCodecNewType` trait to bring the implicit values into scope.
## Monix NewType integration
@@ -171,7 +172,7 @@ schemas for types which extend `NewtypeWrapped` and `NewsubtypeWrapped` annotati
"com.softwaremill.sttp.tapir" %% "tapir-monix-newtype" % "@VERSION@"
```
-Then, `import sttp.tapir.codec.monix.newtype._`, or extend the `sttp.tapir.codec.monix.newtype.TapirCodecMonixNewType` trait to bring the implicit values into scope.
+Then, `import sttp.tapir.codec.monix.newtype.*`, or extend the `sttp.tapir.codec.monix.newtype.TapirCodecMonixNewType` trait to bring the implicit values into scope.
## ZIO Prelude Newtype integration
@@ -206,7 +207,7 @@ type Bar = Bar.Type
// Explicitly provide the base type of your newtype when instantiating the helper, in this case, String.
val BarSupport = TapirNewtype[String](Bar)
-import BarSupport._
+import BarSupport.*
implicitly[Schema[Bar]]
implicitly[PlainCodec[Bar]]
```
@@ -234,9 +235,9 @@ case class Person(name: String, age: Int)
@derive(schema("Type of currency in the country"))
sealed trait Currency
- object Currency {case object CommunisticCurrency extends Currency
+object Currency:
+ case object CommunisticCurrency extends Currency
case class USD(amount: Long) extends Currency
-}
```
The annotation will simply generate a `Schema[T]` for your type `T` and put it into companion object.
@@ -249,11 +250,10 @@ import derevo.derive
import sttp.tapir.derevo.schema
import io.estatico.newtype.macros.newtype
-object types {
+object types:
@derive(schema)
@newtype
case class Amount(i: Int)
-}
```
Resulting schema will be equivalent to `implicitly[Schema[Int]].map(i => Some(types.Amount(i)))`.
diff --git a/doc/endpoint/ios.md b/doc/endpoint/ios.md
index ad58a2390d..93c9d70dd8 100644
--- a/doc/endpoint/ios.md
+++ b/doc/endpoint/ios.md
@@ -53,10 +53,10 @@ other values in tapir, endpoint input/output descriptions are immutable. For exa
parameters, `start` (mandatory) and `limit` (optional) can be written down as:
```scala mdoc:compile-only
-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 io.circe.generic.auto.*
import java.util.UUID
case class User(name: String)
@@ -75,10 +75,10 @@ parameters, but also to define template-endpoints, which can then be further spe
base endpoint for our API, where all paths always start with `/api/v1.0`, and errors are always returned as a json:
```scala mdoc:silent
-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 io.circe.generic.auto.*
case class ErrorInfo(message: String)
@@ -106,7 +106,7 @@ There's a couple of ways to map over an input/output. First, there's the `map[II
which accepts functions which provide the mapping in both directions. For example:
```scala mdoc:silent:reset
-import sttp.tapir._
+import sttp.tapir.*
import java.util.UUID
case class Paging(from: UUID, limit: Option[Int])
@@ -138,7 +138,7 @@ The `Endpoint.mapIn`, `Endpoint.mapInTo` etc. have the same signatures are the o
Inputs and outputs can also be built for case classes using annotations. For example, for the case class `User`
```scala mdoc:silent:reset
-import sttp.tapir.EndpointIO.annotations._
+import sttp.tapir.EndpointIO.annotations.*
case class User(
@query
@@ -151,7 +151,7 @@ case class User(
endpoint input can be generated using macro `EndpointInput.derived[User]` which is equivalent to
```scala mdoc:silent:nest
-import sttp.tapir._
+import sttp.tapir.*
val userInput: EndpointInput[User] =
query[String]("user").and(cookie[Long]("sessionId")).mapTo[User]
@@ -185,7 +185,7 @@ annotation `@header` it has optional parameter to specify alternative name for q
by annotation `@endpointInput`. For example,
```scala mdoc:silent:reset
-import sttp.tapir.EndpointIO.annotations._
+import sttp.tapir.EndpointIO.annotations.*
@endpointInput("books/{year}/{genre}")
case class Book(
diff --git a/doc/endpoint/json.md b/doc/endpoint/json.md
index b9e3c4d6bc..81116b6638 100644
--- a/doc/endpoint/json.md
+++ b/doc/endpoint/json.md
@@ -39,8 +39,8 @@ serialising/deserialising of the body must be part of the [server logic](../serv
A schema can be provided in this case as well:
```scala mdoc:compile-only
-import sttp.tapir._
-import sttp.tapir.generic.auto._
+import sttp.tapir.*
+import sttp.tapir.generic.auto.*
case class MyBody(field: Int)
stringJsonBody.schema(implicitly[Schema[MyBody]].as[String])
```
@@ -56,7 +56,7 @@ To use [Circe](https://github.com/circe/circe), add the following dependency to
Next, import the package (or extend the `TapirJsonCirce` trait, see [MyTapir](../mytapir.md)):
```scala mdoc:compile-only
-import sttp.tapir.json.circe._
+import sttp.tapir.json.circe.*
```
The above import brings into scope the `jsonBody[T]` body input/output description, which creates a codec, given an
@@ -68,10 +68,10 @@ Note that when using Circe's auto derivation, any encoders/decoders for custom t
For example, to automatically generate a JSON codec for a case class:
```scala mdoc:compile-only
-import sttp.tapir._
-import sttp.tapir.json.circe._
-import sttp.tapir.generic.auto._
-import io.circe.generic.auto._
+import sttp.tapir.*
+import sttp.tapir.json.circe.*
+import sttp.tapir.generic.auto.*
+import io.circe.generic.auto.*
case class Book(author: String, title: String, year: Int)
@@ -84,7 +84,7 @@ Circe lets you select an instance of `io.circe.Printer` to configure the way JSO
Tapir uses `Printer.nospaces`, which would render:
```scala mdoc:compile-only
-import io.circe._
+import io.circe.*
Json.obj(
"key1" -> Json.fromString("present"),
@@ -102,14 +102,13 @@ Suppose we would instead want to omit `null`-values from the object and pretty-p
overriding the `jsonPrinter` in `tapir.circe.json.TapirJsonCirce`:
```scala mdoc:compile-only
-import sttp.tapir.json.circe._
+import sttp.tapir.json.circe.*
import io.circe.Printer
-object MyTapirJsonCirce extends TapirJsonCirce {
+object MyTapirJsonCirce extends TapirJsonCirce:
override def jsonPrinter: Printer = Printer.spaces2.copy(dropNullValues = true)
-}
-import MyTapirJsonCirce._
+import MyTapirJsonCirce.*
```
Now the above JSON object will render as
@@ -129,22 +128,21 @@ To use [µPickle](http://www.lihaoyi.com/upickle/) add the following dependency
Next, import the package (or extend the `TapirJsonuPickle` trait, see [MyTapir](../mytapir.md) and add `TapirJsonuPickle` not `TapirCirceJson`):
```scala mdoc:compile-only
-import sttp.tapir.json.upickle._
+import sttp.tapir.json.upickle.*
```
µPickle requires a `ReadWriter` in scope for each type you want to serialize. In order to provide one use the `macroRW` macro in the companion object as follows:
```scala mdoc:compile-only
-import sttp.tapir._
-import sttp.tapir.generic.auto._
-import upickle.default._
-import sttp.tapir.json.upickle._
+import sttp.tapir.*
+import sttp.tapir.generic.auto.*
+import upickle.default.*
+import sttp.tapir.json.upickle.*
case class Book(author: String, title: String, year: Int)
-object Book {
- implicit val rw: ReadWriter[Book] = macroRW
-}
+object Book:
+ given ReadWriter[Book] = macroRW
val bookInput: EndpointIO[Book] = jsonBody[Book]
```
@@ -170,7 +168,7 @@ For **Play 2.9** use:
Next, import the package (or extend the `TapirJsonPlay` trait, see [MyTapir](../mytapir.md) and add `TapirJsonPlay` not `TapirCirceJson`):
```scala mdoc:compile-only
-import sttp.tapir.json.play._
+import sttp.tapir.json.play.*
```
Play JSON requires `Reads` and `Writes` implicit values in scope for each type you want to serialize.
@@ -186,7 +184,7 @@ To use [Spray JSON](https://github.com/spray/spray-json) add the following depen
Next, import the package (or extend the `TapirJsonSpray` trait, see [MyTapir](../mytapir.md) and add `TapirJsonSpray` not `TapirCirceJson`):
```scala mdoc:compile-only
-import sttp.tapir.json.spray._
+import sttp.tapir.json.spray.*
```
Spray JSON requires a `JsonFormat` implicit value in scope for each type you want to serialize.
@@ -202,7 +200,7 @@ To use [Tethys JSON](https://github.com/tethys-json/tethys) add the following de
Next, import the package (or extend the `TapirJsonTethys` trait, see [MyTapir](../mytapir.md) and add `TapirJsonTethys` not `TapirCirceJson`):
```scala mdoc:compile-only
-import sttp.tapir.json.tethysjson._
+import sttp.tapir.json.tethysjson.*
```
Tethys JSON requires `JsonReader` and `JsonWriter` implicit values in scope for each type you want to serialize.
@@ -218,7 +216,7 @@ To use [Jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala) add the f
Next, import the package (or extend the `TapirJsonJsoniter` trait, see [MyTapir](../mytapir.md) and add `TapirJsonJsoniter` not `TapirCirceJson`):
```scala mdoc:compile-only
-import sttp.tapir.json.jsoniter._
+import sttp.tapir.json.jsoniter.*
```
Jsoniter Scala requires `JsonValueCodec` implicit value in scope for each type you want to serialize.
@@ -242,16 +240,16 @@ And one of the implementations:
Next, import the package (or extend the `TapirJson4s` trait, see [MyTapir](../mytapir.md) and add `TapirJson4s` instead of `TapirCirceJson`):
```scala mdoc:compile-only
-import sttp.tapir.json.json4s._
+import sttp.tapir.json.json4s.*
```
Json4s requires `Serialization` and `Formats` implicit values in scope, for example:
```scala
-import org.json4s._
+import org.json4s.*
// ...
-implicit val serialization: Serialization = org.json4s.jackson.Serialization
-implicit val formats: Formats = org.json4s.jackson.Serialization.formats(NoTypeHints)
+given Serialization = org.json4s.jackson.Serialization
+given Formats = org.json4s.jackson.Serialization.formats(NoTypeHints)
```
## Zio JSON
@@ -264,7 +262,7 @@ To use [zio-json](https://github.com/zio/zio-json), add the following dependency
Next, import the package (or extend the `TapirJsonZio` trait, see [MyTapir](../mytapir.md) and add `TapirJsonZio` instead of `TapirCirceJson`):
```scala mdoc:compile-only
-import sttp.tapir.json.zio._
+import sttp.tapir.json.zio.*
```
Zio JSON requires `JsonEncoder` and `JsonDecoder` implicit values in scope for each type you want to serialize.
@@ -274,10 +272,10 @@ Zio JSON requires `JsonEncoder` and `JsonDecoder` implicit values in scope for e
You can specify query parameters in JSON format by using the `jsonQuery` method. For example, using Circe:
```scala mdoc:compile-only
-import sttp.tapir._
-import sttp.tapir.json.circe._
-import sttp.tapir.generic.auto._
-import io.circe.generic.auto._
+import sttp.tapir.*
+import sttp.tapir.json.circe.*
+import sttp.tapir.generic.auto.*
+import io.circe.generic.auto.*
case class Book(author: String, title: String, year: Int)
diff --git a/doc/endpoint/oneof.md b/doc/endpoint/oneof.md
index be328556a6..97c86970e1 100644
--- a/doc/endpoint/oneof.md
+++ b/doc/endpoint/oneof.md
@@ -42,11 +42,11 @@ For example, below is a specification for an endpoint where the error output is
such a specification can then be refined and reused for other endpoints:
```scala
-import sttp.tapir._
-import sttp.tapir.json.circe._
-import sttp.tapir.generic.auto._
+import sttp.tapir.*
+import sttp.tapir.json.circe.*
+import sttp.tapir.generic.auto.*
import sttp.model.StatusCode
-import io.circe.generic.auto._
+import io.circe.generic.auto.*
sealed trait ErrorInfo
case class NotFound(what: String) extends ErrorInfo
@@ -70,11 +70,11 @@ val baseEndpoint = endpoint.errorOut(
Type erasure may prevent a one-of-variant from working properly. The following example will fail at compile time because `Right[NotFound]` and `Right[BadRequest]` will become `Right[Any]`:
```scala mdoc:fail
-import sttp.tapir._
-import sttp.tapir.json.circe._
-import sttp.tapir.generic.auto._
+import sttp.tapir.*
+import sttp.tapir.json.circe.*
+import sttp.tapir.generic.auto.*
import sttp.model.StatusCode
-import io.circe.generic.auto._
+import io.circe.generic.auto.*
case class ServerError(what: String)
@@ -94,11 +94,11 @@ val baseEndpoint = endpoint.errorOut(
The solution is therefore to handwrite a function checking that a value (of type `Any`) is of the correct type:
```scala mdoc:invisible
-import sttp.tapir._
-import sttp.tapir.json.circe._
-import sttp.tapir.generic.auto._
+import sttp.tapir.*
+import sttp.tapir.json.circe.*
+import sttp.tapir.generic.auto.*
import sttp.model.StatusCode
-import io.circe.generic.auto._
+import io.circe.generic.auto.*
case class ServerError(what: String)
@@ -161,7 +161,7 @@ The `.errorOutVariantPrepend` function allows prepending an error out variant, l
a default. This is useful e.g. when providing a more specific error output, than the current one. For example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
trait DomainException {
def help: String
@@ -194,10 +194,10 @@ Each body variant should represent the same content, and hence have the same hig
To describe a body, which can be given as json, xml or plain text, create the following input/output description:
```scala mdoc:compile-only
-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.*
case class User(name: String)
diff --git a/doc/endpoint/pickler.md b/doc/endpoint/pickler.md
index 8850ac7ad3..b7d17fc990 100644
--- a/doc/endpoint/pickler.md
+++ b/doc/endpoint/pickler.md
@@ -18,8 +18,9 @@ Please note that it is available only for Scala 3 and Scala.JS 3.
A pickler can be derived directly using `Pickler.derived[T]`. This will derive both schema and `JsonCodec[T]`:
-```scala
+```scala mdoc:compile-only
import sttp.tapir.json.pickler.*
+import sttp.tapir.Codec.JsonCodec
case class Book(author: String, title: String, year: Int)
@@ -31,7 +32,7 @@ val bookJsonStr = // { "author": "Herman Melville", "title": Moby Dick", "year":
A `given` pickler in scope makes it available for `jsonQuery`, `jsonBody` and `jsonBodyWithRaw`, which need to be imported from the `sttp.tapir.json.pickler` package. For example:
-```scala
+```scala mdoc:compile-only
import sttp.tapir.*
import sttp.tapir.json.pickler.*
@@ -48,11 +49,11 @@ val addBook: PublicEndpoint[Book, Unit, Unit, Any] =
A pickler also be derived using the `derives` keyword directly on a class:
-```scala
+```scala mdoc:compile-only
import sttp.tapir.json.pickler.*
case class Book(author: String, title: String, year: Int) derives Pickler
-val pickler: Pickler[Book] = summon[Pickler]
+val pickler: Pickler[Book] = summon[Pickler[Book]]
```
Picklers for primitive types are available out-of-the-box. For more complex hierarchies, like nested `case class` structures or `enum`s, you'll need to provide picklers for all children (fields, enum cases etc.). Alternatively, you can use automatic derivation described below.
@@ -61,7 +62,7 @@ Picklers for primitive types are available out-of-the-box. For more complex hier
Picklers can be derived at usage side, when required, by adding the auto-derivation import:
-```scala
+```scala
import sttp.tapir.json.pickler.*
import sttp.tapir.json.pickler.generic.auto.*
@@ -81,8 +82,8 @@ However, this can negatively impact compilation performance, as the same pickler
It is possible to configure schema and codec derivation by providing an implicit `sttp.tapir.pickler.PicklerConfiguration`. This configuration allows switching field naming policy to `snake_case`, `kebab_case`, or an arbitrary transformation function, as well as setting the field name/value for the coproduct (sealed hierarchy) type discriminator, which is discussed in details in further sections.
-```scala
-import sttp.tapir.pickler.PicklerConfiguration
+```scala mdoc:compile-only
+import sttp.tapir.json.pickler.PicklerConfiguration
given customConfiguration: PicklerConfiguration =
PicklerConfiguration
@@ -94,8 +95,8 @@ given customConfiguration: PicklerConfiguration =
Pickler derivation for coproduct types (enums with parameters / sealed hierarchies) works automatically, by adding a `$type` discriminator field with the short class name.
-```scala
-import sttp.tapir.pickler.PicklerConfiguration
+```scala mdoc:compile-only
+import sttp.tapir.json.pickler.PicklerConfiguration
// encodes a case object as { "$type": "MyType" }
given PicklerConfiguration = PicklerConfiguration.default
@@ -106,8 +107,8 @@ Selaed hierarchies with all cases being objects are treated differently, conside
A discriminator field can be specified for coproducts by providing it in the configuration; this will be only used during automatic and semi-automatic derivation:
-```scala
-import sttp.tapir.pickler.PicklerConfiguration
+```scala mdoc:compile-only
+import sttp.tapir.json.pickler.PicklerConfiguration
// encodes a case object as { "who_am_i": "full.pkg.path.MyType" }
given customConfiguration: PicklerConfiguration =
@@ -121,16 +122,16 @@ The discriminator will be added as a field to all coproduct child codecs and sch
Finally, if the discriminator is a field that’s defined on the base trait (and hence in each implementation), the schemas can be specified as a custom implicit value using the `Pickler.oneOfUsingField` macro, for example (this will also generate the appropriate mappings):
-```scala
-sealed trait Entity {
+```scala mdoc:compile-only
+sealed trait Entity:
def kind: String
-}
-case class Person(firstName: String, lastName: String) extends Entity {
+
+case class Person(firstName: String, lastName: String) extends Entity:
def kind: String = "person"
-}
-case class Organization(name: String) extends Entity {
+
+case class Organization(name: String) extends Entity:
def kind: String = "org"
-}
+
import sttp.tapir.json.pickler.*
@@ -159,7 +160,7 @@ Tapir schemas and JSON codecs treats following cases as "enumerations":
Such types are handled by `Pickler.derived[T]`: possible values are encoded as simple strings representing the case objects. For example:
-```scala
+```scala
import sttp.tapir.json.pickler.*
enum ColorEnum:
@@ -185,7 +186,7 @@ pResponse.schema
If sealed hierarchy or enum contain case classes with parameters, they are no longer an "enumeration", and will be treated as standard sealed hierarchies (coproducts):
-```scala
+```scala mdoc:compile-only
import sttp.tapir.json.pickler.*
sealed trait ColorEnum
@@ -205,7 +206,7 @@ pResponse.toCodec.encode(
If you need to customize enumeration value encoding, use `Pickler.derivedEnumeration[T]`:
-```scala
+```scala
import sttp.tapir.json.pickler.*
enum ColorEnum:
diff --git a/doc/endpoint/schemas.md b/doc/endpoint/schemas.md
index f31804d563..b2d1a68872 100644
--- a/doc/endpoint/schemas.md
+++ b/doc/endpoint/schemas.md
@@ -27,18 +27,18 @@ perform any [validation](validation.md).
## Automatic derivation
-Schemas for case classes, sealed traits and their children can be recursively derived. Importing `sttp.tapir.generic.auto._`
+Schemas for case classes, sealed traits and their children can be recursively derived. Importing `sttp.tapir.generic.auto.*`
(or extending the `SchemaDerivation` trait) enables fully automatic derivation for `Schema`:
```scala mdoc:silent:reset
import sttp.tapir.Schema
-import sttp.tapir.generic.auto._
+import sttp.tapir.generic.auto.*
case class Parent(child: Child)
case class Child(value: String)
// implicit schema used by codecs
-implicitly[Schema[Parent]]
+summon[Schema[Parent]]
```
If you have a case class which contains some non-standard types (other than strings, number, other case classes,
@@ -46,7 +46,7 @@ collections), you only need to provide implicit schemas for them. Using these, t
Note that when using [datatypes integrations](integrations.md), respective schemas & codecs must also be imported to
enable the derivation, e.g. for [newtype](integrations.html#newtype-integration) you'll have to add
-`import sttp.tapir.codec.newtype._` or extend `TapirCodecNewType`.
+`import sttp.tapir.codec.newtype.*` or extend `TapirCodecNewType`.
## Semi-automatic derivation
@@ -64,8 +64,8 @@ import sttp.tapir.Schema
case class Parent(child: Child)
case class Child(value: String)
-implicit lazy val sChild: Schema[Child] = Schema.derived
-implicit lazy val sParent: Schema[Parent] = Schema.derived
+given Schema[Child] = Schema.derived
+given Schema[Parent] = Schema.derived
```
Note that while schemas for regular types can be safely defined as `val`s, in case of recursive values, the schema
@@ -90,9 +90,8 @@ For example:
```scala mdoc:silent
case class RecursiveTest(data: List[RecursiveTest])
-object RecursiveTest {
+object RecursiveTest:
implicit def f1Schema: Schema[RecursiveTest] = Schema.derived[RecursiveTest]
-}
```
The implicit doesn't have to be defined in the companion object, just anywhere in scope. This applies to cases where
@@ -131,8 +130,7 @@ representation is described in documentation:
```scala mdoc:silent
import sttp.tapir.generic.Configuration
-implicit val customConfiguration: Configuration =
- Configuration.default.withSnakeCaseMemberNames
+given Configuration = Configuration.default.withSnakeCaseMemberNames
```
## Manually providing schemas
@@ -141,12 +139,12 @@ Alternatively, `Schema[_]` values can be defined by hand, either for whole case
For example, here we state that the schema for `MyCustomType` is a `String`:
```scala mdoc:silent
-import sttp.tapir._
+import sttp.tapir.*
case class MyCustomType()
-implicit val schemaForMyCustomType: Schema[MyCustomType] = Schema.string
+given Schema[MyCustomType] = Schema.string
// or, if the low-level representation is e.g. a number
-implicit val anotherSchemaForMyCustomType: Schema[MyCustomType] = Schema(SchemaType.SInteger())
+// given Schema[MyCustomType] = Schema(SchemaType.SInteger())
```
## Sealed traits / coproducts
@@ -172,7 +170,7 @@ during automatic and semi-automatic derivation:
```scala mdoc:silent:reset
import sttp.tapir.generic.Configuration
-implicit val customConfiguration: Configuration =
+given Configuration =
Configuration.default.withDiscriminator("who_am_i")
```
@@ -186,17 +184,17 @@ semi-automatic or automatic derivation; in both cases a custom implicit has to b
one:
```scala mdoc:silent:reset
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.generic.Derived
-import sttp.tapir.generic.auto._
+import sttp.tapir.generic.auto.*
sealed trait MyCoproduct
case class Child1(s: String) extends MyCoproduct
// ... implementations of MyCoproduct ...
-implicit val myCoproductSchema: Schema[MyCoproduct] = {
+given Schema[MyCoproduct] =
val derived = implicitly[Derived[Schema[MyCoproduct]]].value
- derived.schemaType match {
+ derived.schemaType match
case s: SchemaType.SCoproduct[_] => derived.copy(schemaType = s.addDiscriminatorField(
FieldName("myField"),
Schema.string,
@@ -206,8 +204,6 @@ implicit val myCoproductSchema: Schema[MyCoproduct] = {
)
))
case _ => ???
- }
-}
```
Finally, if the discriminator is a field that's defined on the base trait (and hence in each implementation), the
@@ -215,23 +211,22 @@ schemas can be specified as a custom implicit value using the `Schema.oneOfUsing
for example (this will also generate the appropriate mappings):
```scala mdoc:silent:reset
-sealed trait Entity {
+sealed trait Entity:
def kind: String
-}
-case class Person(firstName: String, lastName: String) extends Entity {
+
+case class Person(firstName: String, lastName: String) extends Entity:
def kind: String = "person"
-}
-case class Organization(name: String) extends Entity {
+
+case class Organization(name: String) extends Entity:
def kind: String = "org"
-}
-import sttp.tapir._
+import sttp.tapir.*
val sPerson = Schema.derived[Person]
val sOrganization = Schema.derived[Organization]
-implicit val sEntity: Schema[Entity] =
- Schema.oneOfUsingField[Entity, String](_.kind, _.toString)(
- "person" -> sPerson, "org" -> sOrganization)
+given Schema[Entity] =
+ Schema.oneOfUsingField[Entity, String](_.kind, _.toString)(
+ "person" -> sPerson, "org" -> sOrganization)
```
### Wrapper object discriminators
@@ -245,10 +240,10 @@ sealed trait Entity
case class Person(firstName: String, lastName: String) extends Entity
case class Organization(name: String) extends Entity
-import sttp.tapir._
-import sttp.tapir.generic.auto._ // to derive child schemas
+import sttp.tapir.*
+import sttp.tapir.generic.auto.* // to derive child schemas
-implicit val sEntity: Schema[Entity] = Schema.oneOfWrapped[Entity]
+given Schema[Entity] = Schema.oneOfWrapped[Entity]
```
The names of the field in the wrapper object will be generated using the implicit `Configuration`. If for some reason
@@ -287,13 +282,13 @@ Schemas for products/coproducts (case classes and case class families) can be tr
For example:
```scala mdoc:silent:reset
-import sttp.tapir._
-import sttp.tapir.generic.auto._
+import sttp.tapir.*
+import sttp.tapir.generic.auto.*
import sttp.tapir.generic.Derived
case class Basket(fruits: List[FruitAmount])
case class FruitAmount(fruit: String, amount: Int)
-implicit val customBasketSchema: Schema[Basket] = implicitly[Derived[Schema[Basket]]].value
+given Schema[Basket] = summon[Derived[Schema[Basket]]].value
.modify(_.fruits.each.amount)(_.description("How many fruits?"))
```
@@ -317,21 +312,21 @@ For example, to support an integer wrapped in a value type in a json body, we ne
decoders (if that's the json library that we are using), schema information with validator:
```scala mdoc:silent:reset-object
-import sttp.tapir._
-import sttp.tapir.generic.auto._
-import sttp.tapir.json.circe._
-import io.circe.{ Encoder, Decoder }
-import io.circe.generic.semiauto._
+import sttp.tapir.*
+import sttp.tapir.generic.auto.*
+import sttp.tapir.json.circe.*
+import io.circe.{Encoder, Decoder}
+import io.circe.generic.semiauto.*
case class Amount(v: Int) extends AnyVal
case class FruitAmount(fruit: String, amount: Amount)
-implicit val amountSchema: Schema[Amount] = Schema(SchemaType.SInteger()).validate(Validator.min(1).contramap(_.v))
-implicit val amountEncoder: Encoder[Amount] = Encoder.encodeInt.contramap(_.v)
-implicit val amountDecoder: Decoder[Amount] = Decoder.decodeInt.map(Amount.apply)
+given Schema[Amount] = Schema(SchemaType.SInteger()).validate(Validator.min(1).contramap(_.v))
+given Encoder[Amount] = Encoder.encodeInt.contramap(_.v)
+given Decoder[Amount] = Decoder.decodeInt.map(Amount.apply)
-implicit val decoder: Decoder[FruitAmount] = deriveDecoder[FruitAmount]
-implicit val encoder: Encoder[FruitAmount] = deriveEncoder[FruitAmount]
+given Decoder[FruitAmount] = deriveDecoder[FruitAmount]
+given Encoder[FruitAmount] = deriveEncoder[FruitAmount]
val e: PublicEndpoint[FruitAmount, Unit, Unit, Nothing] =
endpoint.in(jsonBody[FruitAmount])
diff --git a/doc/endpoint/security.md b/doc/endpoint/security.md
index 04679d4246..0fd010c1ae 100644
--- a/doc/endpoint/security.md
+++ b/doc/endpoint/security.md
@@ -21,7 +21,7 @@ using one of the methods from `auth`, and arbitrary "regular" inputs, such as pa
inputs can contain inputs created through `auth`, though typically this shouldn't be the case.
```
-Currently, the following authentication inputs are available (assuming `import sttp.tapir._`):
+Currently, the following authentication inputs are available (assuming `import sttp.tapir.*`):
* `auth.apiKey(anotherInput)`: wraps any other input and designates it as an api key. The input is typically a header,
cookie or a query parameter
@@ -55,8 +55,8 @@ This feature is available for all server backends *except*: `akka-grpc`, `Armeri
Individual endpoints can be annotated with content length limit:
```scala mdoc:compile-only
-import sttp.tapir._
-import sttp.tapir.server.model.EndpointExtensions._
+import sttp.tapir.*
+import sttp.tapir.server.model.EndpointExtensions.*
val limitedEndpoint = endpoint.maxRequestBodyLength(maxBytes = 16384L)
```
diff --git a/doc/endpoint/static.md b/doc/endpoint/static.md
index 98736f7fee..67ec818ee1 100644
--- a/doc/endpoint/static.md
+++ b/doc/endpoint/static.md
@@ -20,43 +20,33 @@ The easiest way to expose static content from the local filesystem is to use the
is parametrised with the path, at which the content should be exposed, as well as the local system path, from which
to read the data.
-Such an endpoint has to be interpreted using your server interpreter. For example, using the [akka-http](../server/akkahttp.md) interpreter:
+Such an endpoint has to be interpreted using your server interpreter. For example, using the [netty-sync](../server/netty.md) interpreter:
```scala mdoc:compile-only
-import akka.http.scaladsl.server.Route
+import sttp.tapir.*
+import sttp.tapir.files.*
+import sttp.tapir.server.netty.sync.NettySyncServer
-import sttp.tapir._
-import sttp.tapir.files._
-import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter
-
-import scala.concurrent.Future
-import scala.concurrent.ExecutionContext.Implicits.global
-
-val filesRoute: Route = AkkaHttpServerInterpreter().toRoute(
- staticFilesGetServerEndpoint[Future]("site" / "static")("/home/static/data")
-)
+NettySyncServer()
+ .addEndpoint(staticFilesGetServerEndpoint("site" / "static")("/home/static/data"))
+ .startAndWait()
```
Using the above endpoint, a request to `/site/static/css/styles.css` will try to read the
`/home/static/data/css/styles.css` file.
-To expose files without a prefix, use `emptyInput`. For example, using the [netty](../server/netty.md) interpreter, the
-below exposes the content of `/var/www` at `http://localhost:8080`:
+To expose files without a prefix, use `emptyInput`. For example, below exposes the content of `/var/www` at
+`http://localhost:8080`:
```scala mdoc:compile-only
-import sttp.tapir.server.netty.NettyFutureServer
+import sttp.tapir.server.netty.sync.NettySyncServer
import sttp.tapir.emptyInput
-import sttp.tapir._
-import sttp.tapir.files._
-
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.Future
+import sttp.tapir.*
+import sttp.tapir.files.*
-NettyFutureServer()
- .port(8080)
- .addEndpoint(staticFilesGetServerEndpoint[Future](emptyInput)("/var/www"))
- .start()
- .flatMap(_ => Future.never)
+NettySyncServer()
+ .addEndpoint(staticFilesGetServerEndpoint(emptyInput)("/var/www"))
+ .startAndWait()
```
A single file can be exposed using `staticFileGetServerEndpoint`.
@@ -79,26 +69,25 @@ Endpoint constructor methods for files and resources can receive optional `FileO
```scala mdoc:compile-only
import sttp.model.headers.ETag
import sttp.tapir.emptyInput
-import sttp.tapir._
-import sttp.tapir.files._
-
-import scala.concurrent.Future
+import sttp.tapir.*
+import sttp.tapir.files.*
+import sttp.shared.Identity
import java.net.URL
-val customETag: Option[RangeValue] => URL => Future[Option[ETag]] = ???
+val customETag: Option[RangeValue] => URL => Option[ETag] = ???
val customFileFilter: List[String] => Boolean = ???
-val options: FilesOptions[Future] =
+val options: FilesOptions[Identity] =
FilesOptions
- .default
+ .default[Identity]
// serves file.txt.gz instead of file.txt if available and Accept-Encoding contains "gzip"
.withUseGzippedIfAvailable
.calculateETag(customETag)
.fileFilter(customFileFilter)
.defaultFile(List("default.md"))
-val endpoint = staticFilesGetServerEndpoint(emptyInput)("/var/www", options)
+val endpoint = staticFilesGetServerEndpoint[Identity](emptyInput)("/var/www", options)
```
## Endpoint description and server logic
@@ -119,11 +108,10 @@ The content of [WebJars](https://www.webjars.org) that are available on the clas
following routes (here using the `/resources` context path):
```scala mdoc:compile-only
-import sttp.tapir._
-import sttp.tapir.files._
-
-import scala.concurrent.Future
+import sttp.tapir.*
+import sttp.tapir.files.*
+import sttp.shared.Identity
-val webJarRoutes = staticResourcesGetServerEndpoint[Future]("resources")(
+val webJarRoutes = staticResourcesGetServerEndpoint[Identity]("resources")(
this.getClass.getClassLoader, "META-INF/resources/webjars")
```
diff --git a/doc/endpoint/streaming.md b/doc/endpoint/streaming.md
index c03d6c3bbc..f0c0777366 100644
--- a/doc/endpoint/streaming.md
+++ b/doc/endpoint/streaming.md
@@ -27,16 +27,14 @@ For example, to specify that the output is an akka-stream, which is a (presumabl
mapping to the `Person` class:
```scala mdoc:silent:reset
-import sttp.tapir._
-import sttp.tapir.generic.auto._
-import sttp.capabilities.akka.AkkaStreams
-import akka.stream.scaladsl._
-import akka.util.ByteString
+import sttp.tapir.*
+import sttp.tapir.generic.auto.*
+import sttp.capabilities.pekko.PekkoStreams
case class Person(name: String)
// copying the derived json schema type
-endpoint.out(streamBody(AkkaStreams)(Schema.derived[List[Person]], CodecFormat.Json()))
+endpoint.out(streamBody(PekkoStreams)(Schema.derived[List[Person]], CodecFormat.Json()))
```
See also the [runnable streaming example](../examples.md).
diff --git a/doc/endpoint/validation.md b/doc/endpoint/validation.md
index 0dd49bd42a..cf36251e88 100644
--- a/doc/endpoint/validation.md
+++ b/doc/endpoint/validation.md
@@ -25,7 +25,7 @@ Validators can also be added to individual inputs/outputs. Behind the scenes, th
to add top-level validators this way, rather than modifying the implicit schemas, for example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
val e = endpoint.in(
query[Int]("amount")
@@ -36,7 +36,7 @@ val e = endpoint.in(
For optional/iterable inputs/outputs, to validate the contained value(s), use:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
query[Option[Int]]("item").validateOption(Validator.min(0))
query[List[Int]]("item").validateIterable(Validator.min(0)) // validates each repeated parameter
@@ -47,12 +47,12 @@ query[List[Int]]("item").validateIterable(Validator.min(0)) // validates each re
Finally, if you are creating a reusable [codec](codecs.md), a validator can be added to it as well:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.CodecFormat.TextPlain
case class MyId(id: String)
-implicit val myIdCodec: Codec[String, MyId, TextPlain] = Codec.string
+given Codec[String, MyId, TextPlain] = Codec.string
.map(MyId(_))(_.id)
.validate(Validator.pattern("^[A-Z].*").contramap(_.id))
```
@@ -95,14 +95,14 @@ converts the enum value to a raw type (typically a string). This can be specifie
For example:
```scala mdoc:silent:reset-object
-import sttp.tapir._
+import sttp.tapir.*
sealed trait Color
case object Blue extends Color
case object Red extends Color
// providing the enum values by hand
-implicit def colorSchema: Schema[Color] = Schema.string.validate(
+given Schema[Color] = Schema.string.validate(
Validator.enumeration(List(Blue, Red), (c: Color) => Some(c.toString.toLowerCase)))
```
diff --git a/doc/endpoint/websockets.md b/doc/endpoint/websockets.md
index 56712d20be..3a6e4a7884 100644
--- a/doc/endpoint/websockets.md
+++ b/doc/endpoint/websockets.md
@@ -14,15 +14,15 @@ For example, here's an endpoint where the requests are strings (hence only text
are parsed/formatted as json:
```scala mdoc:silent
-import sttp.tapir._
-import sttp.capabilities.akka.AkkaStreams
-import sttp.tapir.json.circe._
-import sttp.tapir.generic.auto._
-import io.circe.generic.auto._
+import sttp.tapir.*
+import sttp.capabilities.pekko.PekkoStreams
+import sttp.tapir.json.circe.*
+import sttp.tapir.generic.auto.*
+import io.circe.generic.auto.*
case class Response(msg: String, count: Int)
endpoint.out(
- webSocketBody[String, CodecFormat.TextPlain, Response, CodecFormat.Json](AkkaStreams))
+ webSocketBody[String, CodecFormat.TextPlain, Response, CodecFormat.Json](PekkoStreams))
```
When creating a `webSocketBody`, we need to provide the following parameters:
@@ -39,17 +39,17 @@ decoded, but this can be customised through methods on `webSocketBody`.
Alternatively, it's possible to obtain a raw pipe transforming `WebSocketFrame`s:
```scala mdoc:silent
-import akka.stream.scaladsl.Flow
-import sttp.tapir._
-import sttp.capabilities.akka.AkkaStreams
+import org.apache.pekko.stream.scaladsl.Flow
+import sttp.tapir.*
+import sttp.capabilities.pekko.PekkoStreams
import sttp.capabilities.WebSockets
import sttp.ws.WebSocketFrame
-endpoint.out(webSocketBodyRaw(AkkaStreams)): PublicEndpoint[
+endpoint.out(webSocketBodyRaw(PekkoStreams)): PublicEndpoint[
Unit,
Unit,
Flow[WebSocketFrame, WebSocketFrame, Any],
- AkkaStreams with WebSockets]
+ PekkoStreams with WebSockets]
```
Such a pipe by default doesn't handle ping-pong frames automatically, doesn't concatenate fragmented flames, and
@@ -60,7 +60,7 @@ Request/response schemas can be customised through `.requestsSchema` and `.respo
## Interpreting as a sever
When interpreting a web socket endpoint as a server, the [server logic](../server/logic.md) needs to provide a
-streaming-specific pipe from requests to responses. E.g. in akka's case, this will be `Flow[REQ, RESP, Any]`.
+streaming-specific pipe from requests to responses. E.g. in Pekko's case, this will be `Flow[REQ, RESP, Any]`.
Refer to the documentation of interpreters for more details, as not all interpreters support all settings.
@@ -83,4 +83,4 @@ interpreters.
## Next
-Read on about [datatypes integrations](integrations.md).
\ No newline at end of file
+Read on about [datatypes integrations](integrations.md).
diff --git a/doc/endpoint/xml.md b/doc/endpoint/xml.md
index 4ad792cda0..3720817958 100644
--- a/doc/endpoint/xml.md
+++ b/doc/endpoint/xml.md
@@ -31,24 +31,19 @@ import sttp.tapir.{Codec, EndpointIO, Schema, stringBodyUtf8AnyFormat}
import scala.xml.{NodeSeq, XML}
-trait TapirXmlScalaxb {
+trait TapirXmlScalaxb:
case class XmlElementLabel(label: String)
def xmlBody[T: XMLFormat: Schema](implicit l: XmlElementLabel): EndpointIO.Body[String, T] = stringBodyUtf8AnyFormat(scalaxbCodec[T])
- implicit def scalaxbCodec[T: XMLFormat: Schema](implicit label: XmlElementLabel): XmlCodec[T] = {
+ given (using XmlFormat[T], Schema[T], XmlElementLabel): XmlCodec[T] =
Codec.xml((s: String) =>
- try {
- Value(fromXML[T](XML.loadString(s)))
- } catch {
- case e: Exception => Error(s, e)
- }
+ try Value(fromXML[T](XML.loadString(s)))
+ catch case e: Exception => Error(s, e)
)((t: T) => {
- val nodeSeq: NodeSeq = toXML[T](obj = t, elementLabel = label.label, scope = defaultScope)
+ val nodeSeq: NodeSeq = toXML[T](obj = t, elementLabel = summon[XmlElementLabel].label, scope = defaultScope)
nodeSeq.toString()
})
- }
-}
```
This creates `XmlCodec[T]` that would encode / decode the types with `XMLFormat`, `Schema` and with `XmlElementLabel` provided in scope.
It also introduces `xmlBody` helper method, which allows you to easily express, that the declared endpoint consumes or returns XML.
@@ -70,19 +65,18 @@ Usage example:
import sttp.tapir.{PublicEndpoint, endpoint}
import cats.effect.IO
import generated.Outer // import may differ depending on location of generated code
-import sttp.tapir.generic.auto._ // needed for Schema derivation
+import sttp.tapir.generic.auto.* // needed for Schema derivation
import sttp.tapir.server.ServerEndpoint
-object Endpoints {
- import xml._ // imports tapir related serialization / deserialization logic
+object Endpoints:
+ import xml.* // imports tapir related serialization / deserialization logic
- implicit val label: XmlElementLabel = XmlElementLabel("outer") // `label` is needed by scalaxb code to properly encode the top node of the xml
+ given XmlElementLabel = XmlElementLabel("outer") // `label` is needed by scalaxb code to properly encode the top node of the xml
val xmlEndpoint: PublicEndpoint[Outer, Unit, Outer, Any] = endpoint.post // `Outer` is a class generated by scalaxb based on .xsd file.
.in("xml")
.in(xmlBody[Outer])
.out(xmlBody[Outer])
-}
```
If the generation of OpenAPI documentation is required, consider adding OpenAPI doc extension on schema providing XML
@@ -93,7 +87,7 @@ For more information on adding OpenAPI doc extension in tapir refer to [document
Adding xml namespace doc extension to `Outer`'s `Schema` example:
```scala
case class XmlNamespace(namespace: String)
-implicit val outerSchemaWithXmlNamespace: Schema[Outer] = implicitly[Derived[Schema[Outer]]].value
+given Schema[Outer] = summon[Derived[Schema[Outer]]].value
.docsExtension("xml", XmlNamespace("http://www.example.com/innerouter"))
```
diff --git a/doc/examples.md b/doc/examples.md
index 04090a8a26..704d70b0e2 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 [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.
+The [examples](https://github.com/softwaremill/tapir/tree/master/examples/src/main/scala/sttp/tapir/examples) sub-project contains a number of runnable tapir usage examples, using various interpreters and showcasing different features.
## Generate a tapir project
diff --git a/doc/generator/sbt-openapi-codegen.md b/doc/generator/sbt-openapi-codegen.md
index 69b28c6ea6..96365c94aa 100644
--- a/doc/generator/sbt-openapi-codegen.md
+++ b/doc/generator/sbt-openapi-codegen.md
@@ -50,9 +50,9 @@ openapiAdditionalPackages Nil Addit
The general usage is;
```scala
-import sttp.apispec.openapi.circe.yaml._
-import sttp.tapir.generated._
-import sttp.tapir.docs.openapi._
+import sttp.apispec.openapi.circe.yaml.*
+import sttp.tapir.generated.*
+import sttp.tapir.docs.openapi.*
val docs = TapirGeneratedEndpoints.generatedEndpoints.toOpenAPI("My Bookshop", "1.0")
```
diff --git a/doc/index.md b/doc/index.md
index a2d128bce7..a46fe5fe86 100644
--- a/doc/index.md
+++ b/doc/index.md
@@ -1,7 +1,7 @@
# tapir
-
Declarative, type-safe web endpoints library.
+
Rapid development of self-documenting APIs
@@ -38,6 +38,8 @@ input and output parameters. An endpoint specification can be interpreted as:
Depending on how you prefer to explore the library, take a look at one of the [examples](examples.md) or read on
for a more detailed description of how tapir works! ScalaDocs are available at [javadoc.io](https://www.javadoc.io/doc/com.softwaremill.sttp.tapir).
+Tapir is licensed under Apache2, the source code is [available on GitHub](https://github.com/softwaremill/tapir).
+
## Why tapir?
* **type-safety**: compile-time guarantees, develop-time completions, read-time information
@@ -61,17 +63,6 @@ for a more detailed description of how tapir works! ScalaDocs are available at [
>
```
-## Availability
-
-Tapir is available:
-
-* all modules - Scala 2.12 and 2.13 on the JVM (Java 11+)
-* selected modules - Scala 3 on the JVM (Java 11+)
-* selected modules - Scala 2.12, 2.13 and 3 using Scala.JS
-* selected modules - Scala 2.12, 2.13 and 3 using Scala Native
-
-Tapir is licensed under Apache2, the source code is [available on GitHub](https://github.com/softwaremill/tapir).
-
## Adopters
Is your company already using tapir? We're continually expanding the "adopters" section in the documentation; the more the merrier! It would be great to feature your company's logo, but in order to do that, we'll need written permission to avoid any legal misunderstandings.
@@ -119,10 +110,10 @@ Thank you!
## Code teaser
```scala mdoc:compile-only
-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 io.circe.generic.auto.*
type Limit = Int
type AuthToken = String
@@ -144,17 +135,17 @@ val booksListing: PublicEndpoint[(BooksQuery, Limit, AuthToken), String, List[Bo
// Generate OpenAPI documentation
-import sttp.apispec.openapi.circe.yaml._
+import sttp.apispec.openapi.circe.yaml.*
import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter
val docs = OpenAPIDocsInterpreter().toOpenAPI(booksListing, "My Bookshop", "1.0")
println(docs.toYaml)
-// Convert to akka-http Route
+// Convert to pekko-http Route
-import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter
-import akka.http.scaladsl.server.Route
+import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter
+import org.apache.pekko.http.scaladsl.server.Route
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
@@ -163,14 +154,14 @@ def bookListingLogic(bfy: BooksQuery,
at: AuthToken): Future[Either[String, List[Book]]] =
Future.successful(Right(List(Book("The Sorrows of Young Werther"))))
-val booksListingRoute: Route = AkkaHttpServerInterpreter()
+val booksListingRoute: Route = PekkoHttpServerInterpreter()
.toRoute(booksListing.serverLogic((bookListingLogic _).tupled))
// Convert to sttp Request
import sttp.tapir.client.sttp.SttpClientInterpreter
-import sttp.client3._
+import sttp.client3.*
val booksListingRequest: Request[DecodeResult[Either[String, List[Book]]], Any] =
SttpClientInterpreter()
@@ -208,6 +199,7 @@ We offer commercial support for sttp and related technologies, as well as develo
quickstart
examples
stability
+ scala_2_3_platforms
.. toctree::
:maxdepth: 2
diff --git a/doc/mytapir.md b/doc/mytapir.md
index b7c075abf0..7560a8fda4 100644
--- a/doc/mytapir.md
+++ b/doc/mytapir.md
@@ -9,7 +9,7 @@ a single-import whenever you want to use tapir. For example:
```scala
object MyTapir extends Tapir
- with AkkaHttpServerInterpreter
+ with PekkoHttpServerInterpreter
with SttpClientInterpreter
with OpenAPIDocsInterpreter
with SchemaDerivation
@@ -23,9 +23,9 @@ Then, a single `import MyTapir._` and all Tapir data types and interpreter metho
You might also define an alias for `Endpoint`, with the capabilities that your endpoints use, e.g.:
```scala mdoc:compile-only
-import sttp.capabilities.akka.AkkaStreams
+import sttp.capabilities.pekko.PekkoStreams
import sttp.capabilities.WebSockets
import sttp.tapir.Endpoint
-type MyEndpoint[A, I, E, O] = Endpoint[A, I, E, O, AkkaStreams with WebSockets]
-```
\ No newline at end of file
+type MyEndpoint[A, I, E, O] = Endpoint[A, I, E, O, PekkoStreams with WebSockets]
+```
diff --git a/doc/quickstart.md b/doc/quickstart.md
index a7d80fc392..3fd1414079 100644
--- a/doc/quickstart.md
+++ b/doc/quickstart.md
@@ -13,7 +13,7 @@ Many of tapir functionalities come as builder methods in the main package, hence
you import the main package entirely, i.e.:
```scala
-import sttp.tapir._
+import sttp.tapir.*
```
Finally, type:
@@ -32,4 +32,4 @@ provide type arguments in some cases). In sbt, this is:
```scala
scalacOptions += "-Ypartial-unification"
-```
\ No newline at end of file
+```
diff --git a/doc/scala_2_3_platforms.md b/doc/scala_2_3_platforms.md
new file mode 100644
index 0000000000..5534455db6
--- /dev/null
+++ b/doc/scala_2_3_platforms.md
@@ -0,0 +1,53 @@
+# Scala 2, Scala 3 & platforms
+
+Tapir is available for Scala 3.3+, Scala 2.13 and Scala 2.12, on the JVM, JS and Native platforms.
+
+Note that some modules are unavailable for specific combinations of the above, specifically for Scala.JS and Scala
+Native. The JVM modules require Java 11+, with a couple of exceptions, which require Java 21+ - this is marked in
+the documentation.
+
+## In the documentation & examples
+
+The documentation & examples are written & compiled using Scala 3. To compile example code with Scala 2, some
+adjustments might be necessary:
+
+* For wildcard imports, use `_` instead of `*`, e.g. instead of `import sttp.tapir.*`, use `import sttp.tapir._`
+* For the main method, instead of `@main`, use an `object MyApp extends App`, e.g.:
+
+```scala
+// in Scala 3:
+@main def myExample(): Unit = /* body */
+
+// in Scala 2:
+object MyExample extends App {
+ /* body */
+}
+```
+
+* Instead of `given` definitions, use `implicit val` or `implicit def` (for codecs, schemas etc.). E.g.:
+
+```scala
+// in Scala 3:
+given Schema[MyType] = Schema.derived
+
+// in Scala 2:
+implicit val myTypeSchema: Schema[MyType] = Schema.derived
+```
+
+* Use curly braces around class & method definitions. E.g.:
+
+```scala
+// in Scala 3:
+class MyClass:
+ def myMethod(): Unit =
+ val z = 2
+ z + 2
+
+// in Scala 2:
+class MyClass {
+ def myMethod(): Unit = {
+ val z = 2
+ z + 2
+ }
+}
+```
diff --git a/doc/server/akkahttp.md b/doc/server/akkahttp.md
index 9f892737e3..1531181d64 100644
--- a/doc/server/akkahttp.md
+++ b/doc/server/akkahttp.md
@@ -16,7 +16,7 @@ your own Akka version (for example 2.5), use sbt exclusion. Mind the Scala versi
Now import the object:
-```scala mdoc:compile-only
+```scala
import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter
```
@@ -27,7 +27,7 @@ The `toRoute` method requires a single, or a list of `ServerEndpoint`s, which ca
For example:
-```scala mdoc:compile-only
+```scala
import sttp.tapir._
import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter
import scala.concurrent.Future
@@ -55,7 +55,7 @@ tapir-generated directive.
Edge-case endpoints, which require special logic not expressible using tapir, can be implemented directly
using akka-http. For example:
-```scala mdoc:compile-only
+```scala
import sttp.tapir._
import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter
import akka.http.scaladsl.server._
@@ -101,7 +101,7 @@ The interpreter supports [SSE (Server Sent Events)](https://developer.mozilla.or
For example, to define an endpoint that returns event stream:
-```scala mdoc:compile-only
+```scala
import akka.stream.scaladsl.Source
import sttp.model.sse.ServerSentEvent
import sttp.tapir._
diff --git a/doc/server/armeria.md b/doc/server/armeria.md
index 21994dcdcd..9afac992f3 100644
--- a/doc/server/armeria.md
+++ b/doc/server/armeria.md
@@ -22,32 +22,28 @@ The `toService` method require a single, or a list of `ServerEndpoint`s, which c
[server logic](logic.md) to an endpoint.
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.armeria.ArmeriaFutureServerInterpreter
import scala.concurrent.Future
import com.linecorp.armeria.server.Server
-object Main {
- // JVM entry point that starts the HTTP server
- def main(args: Array[String]): Unit = {
- val tapirEndpoint: PublicEndpoint[(String, Int), Unit, String, Any] = ??? // your definition here
- def logic(s: String, i: Int): Future[Either[Unit, String]] = ??? // your logic here
- val tapirService = ArmeriaFutureServerInterpreter().toService(tapirEndpoint.serverLogic((logic _).tupled))
- val server = Server
- .builder()
- .service(tapirService) // your endpoint is bound to the server
- .build()
- server.start().join()
- }
-}
+// JVM entry point that starts the HTTP server - uncommment @main to run
+/* @main */ def armeriaSerer(): Unit =
+ val tapirEndpoint: PublicEndpoint[(String, Int), Unit, String, Any] = ??? // your definition here
+ def logic(s: String, i: Int): Future[Either[Unit, String]] = ??? // your logic here
+ val tapirService = ArmeriaFutureServerInterpreter().toService(tapirEndpoint.serverLogic((logic _).tupled))
+ val server = Server
+ .builder()
+ .service(tapirService) // your endpoint is bound to the server
+ .build()
+ server.start().join()
```
This interpreter also supports streaming using Armeria Streams which is fully compatible with Reactive Streams:
```scala mdoc:compile-only
-
import sttp.capabilities.armeria.ArmeriaStreams
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.armeria.ArmeriaFutureServerInterpreter
import scala.concurrent.Future
import com.linecorp.armeria.common.HttpData
@@ -60,9 +56,8 @@ val streamingResponse: PublicEndpoint[Int, Unit, Publisher[HttpData], ArmeriaStr
.in(query[Int]("key"))
.out(streamTextBody(ArmeriaStreams)(CodecFormat.TextPlain()))
-def streamLogic(foo: Int): Future[Publisher[HttpData]] = {
+def streamLogic(foo: Int): Future[Publisher[HttpData]] =
Future.successful(StreamMessage.of(HttpData.ofUtf8("hello"), HttpData.ofUtf8("world")))
-}
val tapirService = ArmeriaFutureServerInterpreter().toService(streamingResponse.serverLogicSuccess(streamLogic))
```
@@ -89,14 +84,15 @@ This object contains the `toService(e: ServerEndpoint[Fs2Streams[F], F])` method
An HTTP server can then be started as in the following example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.armeria.cats.ArmeriaCatsServerInterpreter
-import cats.effect._
+import cats.effect.*
import cats.effect.std.Dispatcher
import com.linecorp.armeria.server.Server
+import java.util.concurrent.CompletableFuture
-object Main extends IOApp {
- override def run(args: List[String]): IO[ExitCode] = {
+object Main extends IOApp:
+ override def run(args: List[String]): IO[ExitCode] =
val tapirEndpoint: PublicEndpoint[String, Unit, String, Any] = ???
def logic(req: String): IO[Either[Unit, String]] = ???
@@ -117,23 +113,21 @@ object Main extends IOApp {
}
}
)({ server =>
- IO.fromCompletableFuture(IO(server.closeAsync())).void
+ IO.fromCompletableFuture(IO(server.closeAsync().asInstanceOf[CompletableFuture[Unit]]))
})
}
.use(_ => IO.never)
- }
-}
```
This interpreter also supports streaming using FS2 streams:
```scala mdoc:compile-only
import sttp.capabilities.fs2.Fs2Streams
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.armeria.cats.ArmeriaCatsServerInterpreter
-import cats.effect._
+import cats.effect.*
import cats.effect.std.Dispatcher
-import fs2._
+import fs2.*
val streamingResponse: Endpoint[Unit, Int, Unit, Stream[IO, Byte], Fs2Streams[IO]] =
endpoint
@@ -141,9 +135,8 @@ val streamingResponse: Endpoint[Unit, Int, Unit, Stream[IO, Byte], Fs2Streams[IO
.in(query[Int]("times"))
.out(streamTextBody(Fs2Streams[IO])(CodecFormat.TextPlain()))
-def streamLogic(times: Int): IO[Stream[IO, Byte]] = {
+def streamLogic(times: Int): IO[Stream[IO, Byte]] =
IO.pure(Stream.chunk(Chunk.array("Hello world!".getBytes)).repeatN(times))
-}
def dispatcher: Dispatcher[IO] = ???
@@ -170,14 +163,15 @@ An HTTP server can then be started as in the following example:
```scala mdoc:compile-only
import com.linecorp.armeria.server.Server
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.armeria.zio.ArmeriaZioServerInterpreter
-import sttp.tapir.ztapir._
+import sttp.tapir.ztapir.*
import zio.{ExitCode, Runtime, UIO, URIO, ZIO, ZIOAppDefault}
+import java.util.concurrent.CompletableFuture
-object Main extends ZIOAppDefault {
- override def run: URIO[Any, ExitCode] = {
- implicit val runtime = Runtime.default
+object Main extends ZIOAppDefault:
+ override def run: URIO[Any, ExitCode] =
+ given Runtime[Any] = Runtime.default
val tapirEndpoint: PublicEndpoint[String, Unit, String, Any] = ???
def logic(key: String): UIO[String] = ???
@@ -191,9 +185,8 @@ object Main extends ZIOAppDefault {
server.start().thenApply[Server](_ => server)
}
- ZIO.scoped(ZIO.acquireRelease(s)(server => ZIO.fromCompletableFuture(server.closeAsync()).orDie) *> ZIO.never).exitCode
- }
-}
+ ZIO.scoped(ZIO.acquireRelease(s)(server =>
+ ZIO.fromCompletableFuture(server.closeAsync().asInstanceOf[CompletableFuture[Unit]]).orDie) *> ZIO.never).exitCode
```
This interpreter supports streaming using ZStreams.
diff --git a/doc/server/aws.md b/doc/server/aws.md
index 40ce4536e7..aaeea4a751 100644
--- a/doc/server/aws.md
+++ b/doc/server/aws.md
@@ -5,12 +5,9 @@ an [AWS Lambda](https://docs.aws.amazon.com/apigateway/latest/developerguide/htt
This approach, known as the Fat Lambda function, utilizes a single lambda function for deploying multiple endpoints. To invoke the
function, HTTP requests can be proxied through [AWS API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html).
+To configure API Gateway routes, and the Lambda function, tools like [AWS SAM](https://aws.amazon.com/serverless/sam/), [AWS CDK](https://aws.amazon.com/cdk/) or [Terraform](https://www.terraform.io/) can be used, to automate cloud deployments.
-To configure API Gateway routes, and the Lambda function, tools like [AWS SAM](https://aws.amazon.com/serverless/sam/)
-, [AWS CDK](https://aws.amazon.com/cdk/) or [Terraform](https://www.terraform.io/) can be used, to automate cloud deployments.
-
-For an overview of how this works in more detail, see [this blog post](https://blog.softwaremill.com/tapir-serverless-a-proof-of-concept-6b8c9de4d396)
-.
+For an overview of how this works in more detail, see [this blog post](https://blog.softwaremill.com/tapir-serverless-a-proof-of-concept-6b8c9de4d396).
## Runtime & Server interpreters
diff --git a/doc/server/errors.md b/doc/server/errors.md
index eb8a2503e4..3e9b5dcc20 100644
--- a/doc/server/errors.md
+++ b/doc/server/errors.md
@@ -21,12 +21,12 @@ If the business logic signals errors as exceptions, some or all can be recovered
For example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.netty.NettyFutureServerInterpreter
-import scala.concurrent.Future
-import scala.util._
+import scala.concurrent.{ExecutionContext, Future}
+import scala.util.*
-implicit val ec = scala.concurrent.ExecutionContext.global
+given ExecutionContext = ExecutionContext.global
type ErrorInfo = String
def logic(s: String): Future[Int] = ???
@@ -118,13 +118,13 @@ Moreover, when using the `DefaultDecodeFailureHandler`, decode failure handling
basis, by setting an attribute. For example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
// bringing into scope the onDecodeFailureNextEndpoint extension method
-import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler.OnDecodeFailure._
+import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler.OnDecodeFailure.*
case class UserId(value: String)
-object UserId {
- implicit val codec: Codec[String, UserId, CodecFormat.TextPlain] = Codec.string.mapDecode(raw =>
+object UserId:
+ given Codec[String, UserId, CodecFormat.TextPlain] = Codec.string.mapDecode(raw =>
UserId.make(raw) match {
case Left(error) =>
DecodeResult.Error(raw, new IllegalArgumentException(s"Invalid User value ($raw), failed with $error"))
@@ -134,7 +134,6 @@ object UserId {
def make(in: String): Either[String, UserId] =
if (in.length > 5) Right(new UserId(in))
else Left("Too short")
-}
// If your codec for UserId fails, allow checking other endpoints for possible matches, like /customer/some_special_case
endpoint.in("customer" / path[UserId]("user_id").onDecodeFailureNextEndpoint)
@@ -151,12 +150,12 @@ default ones for you.
We'll need to provide both the endpoint output which should be used for error messages, along with the output's value:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.model.ValuedEndpointOutput
import sttp.tapir.server.netty.NettyFutureServerOptions
-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 io.circe.generic.auto.*
case class MyFailure(msg: String)
def myFailureResponse(m: String): ValuedEndpointOutput[_] =
diff --git a/doc/server/finatra.md b/doc/server/finatra.md
index 9876d4a79e..eb3f426ac6 100644
--- a/doc/server/finatra.md
+++ b/doc/server/finatra.md
@@ -9,7 +9,7 @@ dependency:
and import the object:
-```scala mdoc:compile-only
+```scala
import sttp.tapir.server.finatra.FinatraServerInterpreter
```
@@ -22,7 +22,7 @@ Or, if you would like to use cats-effect project, you can add the following depe
and import the object:
-```scala mdoc:compile-only
+```scala
import sttp.tapir.server.finatra.cats.FinatraCatsServerInterpreter
```
@@ -33,7 +33,7 @@ The `toRoute` method on the interpreter requires a `ServerEndpoint`, which can b
For example:
-```scala mdoc:compile-only
+```scala
import sttp.tapir._
import sttp.tapir.server.finatra.{ FinatraServerInterpreter, FinatraRoute }
import com.twitter.util.Future
@@ -50,7 +50,7 @@ val countCharactersRoute: FinatraRoute =
or a cats-effect's example:
-```scala mdoc:compile-only
+```scala
import cats.effect.IO
import cats.effect.std.Dispatcher
import sttp.tapir._
@@ -72,7 +72,7 @@ val countCharactersRoute: FinatraRoute =
Now that you've created the `FinatraRoute`, add `TapirController` as a trait to your `Controller`. You can then
add the created route with `addTapirRoute`.
-```scala mdoc:compile-only
+```scala
import sttp.tapir.server.finatra._
import com.twitter.finatra.http.Controller
@@ -80,4 +80,4 @@ val aRoute: FinatraRoute = ???
class MyController extends Controller with TapirController {
addTapirRoute(aRoute)
}
-```
\ No newline at end of file
+```
diff --git a/doc/server/http4s.md b/doc/server/http4s.md
index 693f4ba28d..84f5ed92dc 100644
--- a/doc/server/http4s.md
+++ b/doc/server/http4s.md
@@ -19,7 +19,7 @@ The `toRoutes` and `toHttp` methods require a single, or a list of `ServerEndpoi
The server logic should use a cats-effect-support `F[_]` effect type. For example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.http4s.Http4sServerInterpreter
import cats.effect.IO
import org.http4s.HttpRoutes
@@ -71,17 +71,17 @@ using `withHttpWebSocketApp`, for example:
```scala mdoc:compile-only
import sttp.capabilities.WebSockets
import sttp.capabilities.fs2.Fs2Streams
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.http4s.Http4sServerInterpreter
import cats.effect.IO
import org.http4s.HttpRoutes
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.server.Router
import org.http4s.server.websocket.WebSocketBuilder2
-import fs2._
+import fs2.*
import scala.concurrent.ExecutionContext
-implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
+given ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
val wsEndpoint: PublicEndpoint[Unit, Unit, Pipe[IO, String, String], Fs2Streams[IO] with WebSockets] =
endpoint.get.in("count").out(webSocketBody[String, CodecFormat.TextPlain, String, CodecFormat.TextPlain](Fs2Streams[IO]))
@@ -90,7 +90,7 @@ val wsRoutes: WebSocketBuilder2[IO] => HttpRoutes[IO] =
Http4sServerInterpreter[IO]().toWebSocketRoutes(wsEndpoint.serverLogicSuccess[IO](_ => ???))
BlazeServerBuilder[IO]
- .withExecutionContext(ec)
+ .withExecutionContext(summon[ExecutionContext])
.bindHttp(8080, "localhost")
.withHttpWebSocketApp(wsb => Router("/" -> wsRoutes(wsb)).orNotFound)
```
@@ -104,7 +104,7 @@ For example, to define an endpoint that returns event stream:
```scala mdoc:compile-only
import cats.effect.IO
import sttp.model.sse.ServerSentEvent
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.http4s.{Http4sServerInterpreter, serverSentEventsBody}
val sseEndpoint = endpoint.get.out(serverSentEventsBody[IO])
@@ -123,8 +123,8 @@ with a dedicated context-extracting input, `.contextIn`. Endpoints using such in
For example:
```scala mdoc:compile-only
-import sttp.tapir._
-import sttp.tapir.server.http4s._
+import sttp.tapir.*
+import sttp.tapir.server.http4s.*
import cats.effect.IO
import org.http4s.ContextRoutes
diff --git a/doc/server/jdkhttp.md b/doc/server/jdkhttp.md
index 4e12f70be7..bdcef50420 100644
--- a/doc/server/jdkhttp.md
+++ b/doc/server/jdkhttp.md
@@ -11,7 +11,7 @@ To expose endpoints using the
Then, import the package:
```scala
-import sttp.tapir.server.jdkhttp._
+import sttp.tapir.server.jdkhttp.*
```
and use `JdkHttpServer().addEndpoints` to expose server endpoints.
@@ -25,8 +25,8 @@ original `serverLogic` methods and also because names are shorter.
For example:
```scala mdoc:compile-only
-import sttp.tapir._
-import sttp.tapir.server.jdkhttp._
+import sttp.tapir.*
+import sttp.tapir.server.jdkhttp.*
val helloWorld = endpoint
.get
diff --git a/doc/server/logic.md b/doc/server/logic.md
index c76b1d8136..1018756ac9 100644
--- a/doc/server/logic.md
+++ b/doc/server/logic.md
@@ -38,7 +38,7 @@ converted to a function using a single argument using `.tupled`, or that you'll
to extract the parameters:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.ServerEndpoint
import scala.concurrent.Future
@@ -63,7 +63,7 @@ Both a single server endpoint, and multiple endpoints can be interpreted as a se
endpoints can be converted to a Netty route:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.netty.{NettyFutureServerInterpreter, FutureRoute}
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
@@ -90,13 +90,13 @@ errors which are subtypes of `E`. Any others will be propagated without changes.
For example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import scala.concurrent.Future
case class MyError(msg: String) extends Exception
val testEndpoint = endpoint
.in(query[Boolean]("fail"))
- .errorOut(stringBody.map(MyError)(_.msg))
+ .errorOut(stringBody.map(MyError(_))(_.msg))
.out(stringBody)
.serverLogicRecoverErrors { fail =>
if (fail) {
@@ -141,11 +141,11 @@ provided.
For example, we can create a partial server endpoint given the security logic, and an endpoint with security inputs:
```scala mdoc:silent
-import sttp.tapir._
-import sttp.tapir.server._
-import scala.concurrent.Future
+import sttp.tapir.*
+import sttp.tapir.server.*
+import scala.concurrent.{ExecutionContext, Future}
-implicit val ec = scala.concurrent.ExecutionContext.global
+implicit val ec: ExecutionContext = ExecutionContext.global
case class User(name: String)
def authLogic(token: String): Future[Either[Int, User]] = Future {
@@ -207,8 +207,8 @@ with an error output (for security errors) and the security logic to add. This a
before the security logic defined in the endpoint so far (if any). For example:
```scala mdoc:compile-only
-import sttp.tapir._
-import sttp.tapir.files._
+import sttp.tapir.*
+import sttp.tapir.files.*
import scala.concurrent.Future
import sttp.model.StatusCode
diff --git a/doc/server/netty.md b/doc/server/netty.md
index f16dd47a1b..4cbeafa9d0 100644
--- a/doc/server/netty.md
+++ b/doc/server/netty.md
@@ -29,7 +29,7 @@ to an endpoint.
For example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
@@ -92,14 +92,14 @@ To create a web socket endpoint, use Tapir's `out(webSocketBody)` output type:
```scala mdoc:compile-only
import cats.effect.kernel.Resource
import cats.effect.{IO, ResourceApp}
-import cats.syntax.all._
+import cats.syntax.all.*
import fs2.Pipe
import sttp.capabilities.fs2.Fs2Streams
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.netty.cats.NettyCatsServer
import sttp.ws.WebSocketFrame
-import scala.concurrent.duration._
+import scala.concurrent.duration.*
object WebSocketsNettyCatsServer extends ResourceApp.Forever {
@@ -159,7 +159,7 @@ You can customize this behavior in `NettyConfig`:
```scala mdoc:compile-only
import sttp.tapir.server.netty.NettyConfig
-import scala.concurrent.duration._
+import scala.concurrent.duration.*
// adjust the waiting time to your needs
val config = NettyConfig.default.withGracefulShutdownTimeout(5.seconds)
@@ -172,8 +172,8 @@ val config2 = NettyConfig.default.noGracefulShutdown
There is possibility to use Domain socket instead of TCP for handling traffic.
```scala mdoc:compile-only
+import sttp.tapir.*
import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureDomainSocketBinding}
-import sttp.tapir.{endpoint, query, stringBody}
import java.nio.file.Paths
import scala.concurrent.ExecutionContext.Implicits.global
diff --git a/doc/server/nima.md b/doc/server/nima.md
index 23661ed8e8..1f684170e9 100644
--- a/doc/server/nima.md
+++ b/doc/server/nima.md
@@ -19,7 +19,7 @@ Such endpoints are then processed through `NimaServerInterpreter` in order to ob
```scala
import io.helidon.webserver.WebServer
-import sttp.tapir._
+import sttp.tapir.*
import sttp.shared.Identity
import sttp.tapir.server.nima.NimaServerInterpreter
diff --git a/doc/server/observability.md b/doc/server/observability.md
index b14c0ab38f..8722a97f94 100644
--- a/doc/server/observability.md
+++ b/doc/server/observability.md
@@ -251,14 +251,12 @@ libraryDependencies += "dev.zio" %% "zio-metrics-connectors" % "2.0.0-RC6"
Example zio metrics prometheus publisher style tapir metrics endpoint.
```scala
import sttp.tapir.{endpoint, stringBody}
-import zio._
+import zio.*
import zio.metrics.connectors.MetricsConfig
import zio.metrics.connectors.prometheus.{PrometheusPublisher, prometheusLayer, publisherLayer}
import zio.metrics.jvm.DefaultJvmMetrics
-
-object ZioEndpoint {
-
+object ZioEndpoint:
/** DefaultJvmMetrics.live.orDie >+> is optional if you want JVM metrics */
private val layer = DefaultJvmMetrics.live.orDie >+> ZLayer.make[PrometheusPublisher](
ZLayer.succeed(MetricsConfig(1.seconds)),
@@ -279,5 +277,4 @@ object ZioEndpoint {
val metricsEndpoint =
endpoint.get.in("metrics").out(stringBody).serverLogicSuccess(_ => getMetricsEffect)
-}
```
diff --git a/doc/server/options.md b/doc/server/options.md
index 1e97d4e4fa..ccab80a4aa 100644
--- a/doc/server/options.md
+++ b/doc/server/options.md
@@ -10,23 +10,23 @@ Each interpreter can be configured using an options object, which includes:
* additional user-provided [interceptors](interceptors.md)
To use custom server options pass them as an argument to the interpreter's `apply` method.
-For example, for `AkkaHttpServerOptions` and `AkkaHttpServerInterpreter`:
+For example, for `PekkoHttpServerOptions` and `PekkoHttpServerInterpreter`:
```scala mdoc:compile-only
import sttp.tapir.server.interceptor.decodefailure.DecodeFailureHandler
-import sttp.tapir.server.akkahttp.AkkaHttpServerOptions
-import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter
+import sttp.tapir.server.pekkohttp.PekkoHttpServerOptions
+import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
val customDecodeFailureHandler: DecodeFailureHandler[Future] = ???
-val customServerOptions: AkkaHttpServerOptions = AkkaHttpServerOptions
+val customServerOptions: PekkoHttpServerOptions = PekkoHttpServerOptions
.customiseInterceptors
.decodeFailureHandler(customDecodeFailureHandler)
.options
-
-AkkaHttpServerInterpreter(customServerOptions)
+
+PekkoHttpServerInterpreter(customServerOptions)
```
## Hiding authenticated endpoints
@@ -39,17 +39,17 @@ returned instead by using a different decode failure handler. For example, using
```scala mdoc:compile-only
import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler
-import sttp.tapir.server.akkahttp.AkkaHttpServerOptions
-import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter
+import sttp.tapir.server.pekkohttp.PekkoHttpServerOptions
+import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
-val customServerOptions: AkkaHttpServerOptions = AkkaHttpServerOptions
+val customServerOptions: PekkoHttpServerOptions = PekkoHttpServerOptions
.customiseInterceptors
.decodeFailureHandler(DefaultDecodeFailureHandler.hideEndpointsWithAuth[Future])
.options
-
-AkkaHttpServerInterpreter(customServerOptions)
+
+PekkoHttpServerInterpreter(customServerOptions)
```
Note however, that it can still be possible to discover the existence of certain endpoints using timing attacks.
diff --git a/doc/server/pekkohttp.md b/doc/server/pekkohttp.md
index 0c66500297..d9f3d533a2 100644
--- a/doc/server/pekkohttp.md
+++ b/doc/server/pekkohttp.md
@@ -28,7 +28,7 @@ The `toRoute` method requires a single, or a list of `ServerEndpoint`s, which ca
For example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter
import scala.concurrent.Future
import org.apache.pekko.http.scaladsl.server.Route
@@ -56,9 +56,9 @@ Edge-case endpoints, which require special logic not expressible using tapir, ca
using pekko-http. For example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter
-import org.apache.pekko.http.scaladsl.server._
+import org.apache.pekko.http.scaladsl.server.*
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
@@ -103,7 +103,7 @@ For example, to define an endpoint that returns event stream:
```scala mdoc:compile-only
import org.apache.pekko.stream.scaladsl.Source
import sttp.model.sse.ServerSentEvent
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.pekkohttp.{PekkoHttpServerInterpreter, serverSentEventsBody}
import scala.concurrent.Future
diff --git a/doc/server/play.md b/doc/server/play.md
index cb39680308..295a346d48 100644
--- a/doc/server/play.md
+++ b/doc/server/play.md
@@ -58,13 +58,13 @@ The `toRoutes` method requires a single, or a list of `ServerEndpoint`s, which c
```scala mdoc:compile-only
import org.apache.pekko.stream.Materializer
import play.api.routing.Router.Routes
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.play.PlayServerInterpreter
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
-implicit val materializer: Materializer = ???
+given Materializer = ???
def countCharacters(s: String): Future[Either[Unit, Int]] =
Future(Right[Unit, Int](s.length))
@@ -90,22 +90,19 @@ diverges a bit comparing to other interpreters.
An HTTP server can then be started as in the following example:
```scala
-import play.core.server._
+import play.core.server.*
import play.api.routing.Router.Routes
val aRoute: Routes = ???
-object Main {
- // JVM entry point that starts the HTTP server
- def main(args: Array[String]): Unit = {
- val playConfig = ServerConfig(port =
- sys.props.get("http.port").map(_.toInt).orElse(Some(9000))
- )
- NettyServer.fromRouterWithComponents(playConfig) { components =>
- aRoute
- }
+// JVM entry point that starts the HTTP server - uncomment @main to run
+/* @main */ def playServer(): Unit =
+ val playConfig = ServerConfig(port =
+ sys.props.get("http.port").map(_.toInt).orElse(Some(9000))
+ )
+ NettyServer.fromRouterWithComponents(playConfig) { components =>
+ aRoute
}
-}
```
### As part of an existing Play application
@@ -118,11 +115,9 @@ First, add a line like following in the `routes` files:
```
Then create a class like this:
```scala
-class ApiRouter @Inject() () extends SimpleRouter {
- override def routes: Routes = {
+class ApiRouter @Inject() () extends SimpleRouter:
+ override def routes: Routes =
anotherRoutes.orElse(tapirGeneratedRoutes)
- }
-}
```
Find more details about how to bind a `Router` to your application in the [Play framework documentation](https://www.playframework.com/documentation/2.8.x/ScalaSirdRouter#Binding-sird-Router).
diff --git a/doc/server/vertx.md b/doc/server/vertx.md
index 7bd65d41bc..4d39ee5883 100644
--- a/doc/server/vertx.md
+++ b/doc/server/vertx.md
@@ -14,7 +14,7 @@ to use this interpreter with `Future`.
Then import the object:
```scala mdoc:compile-only
-import sttp.tapir.server.vertx.VertxFutureServerInterpreter._
+import sttp.tapir.server.vertx.VertxFutureServerInterpreter.*
```
This object contains the following methods:
@@ -26,27 +26,24 @@ In practice, routes will be mounted on a router, this router can then be used as
An HTTP server can then be started as in the following example:
```scala mdoc:compile-only
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.vertx.VertxFutureServerInterpreter
-import sttp.tapir.server.vertx.VertxFutureServerInterpreter._
+import sttp.tapir.server.vertx.VertxFutureServerInterpreter.*
import io.vertx.core.Vertx
-import io.vertx.ext.web._
+import io.vertx.ext.web.*
import scala.concurrent.{Await, Future}
-import scala.concurrent.duration._
-
-object Main {
- // JVM entry point that starts the HTTP server
- def main(args: Array[String]): Unit = {
- val vertx = Vertx.vertx()
- val server = vertx.createHttpServer()
- val router = Router.router(vertx)
- val anEndpoint: PublicEndpoint[(String, Int), Unit, String, Any] = ??? // your definition here
- def logic(s: String, i: Int): Future[Either[Unit, String]] = ??? // your logic here
- val attach = VertxFutureServerInterpreter().route(anEndpoint.serverLogic((logic _).tupled))
- attach(router) // your endpoint is now attached to the router, and the route has been created
- Await.result(server.requestHandler(router).listen(9000).asScala, Duration.Inf)
- }
-}
+import scala.concurrent.duration.*
+
+// JVM entry point that starts the HTTP server - uncomment @main to run
+/* @main */ def vertxServer(): Unit =
+ val vertx = Vertx.vertx()
+ val server = vertx.createHttpServer()
+ val router = Router.router(vertx)
+ val anEndpoint: PublicEndpoint[(String, Int), Unit, String, Any] = ??? // your definition here
+ def logic(s: String, i: Int): Future[Either[Unit, String]] = ??? // your logic here
+ val attach = VertxFutureServerInterpreter().route(anEndpoint.serverLogic((logic _).tupled))
+ attach(router) // your endpoint is now attached to the router, and the route has been created
+ Await.result(server.requestHandler(router).listen(9000).asScala, Duration.Inf)
```
## Configuration
@@ -69,7 +66,7 @@ to use this interpreter with Cats Effect typeclasses.
Then import the object:
```scala mdoc:compile-only
-import sttp.tapir.server.vertx.cats.VertxCatsServerInterpreter._
+import sttp.tapir.server.vertx.cats.VertxCatsServerInterpreter.*
```
This object contains the `route[F[_]](e: ServerEndpoint[Fs2Streams[F], F])` method, which returns a function `Router => Route` that will create a route, with a handler attached, matching the endpoint definition. Errors will be recovered automatically.
@@ -77,15 +74,15 @@ This object contains the `route[F[_]](e: ServerEndpoint[Fs2Streams[F], F])` meth
Here is simple example which starts HTTP server with one route:
```scala mdoc:compile-only
-import cats.effect._
+import cats.effect.*
import cats.effect.std.Dispatcher
import io.vertx.core.Vertx
import io.vertx.ext.web.Router
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.vertx.cats.VertxCatsServerInterpreter
-import sttp.tapir.server.vertx.cats.VertxCatsServerInterpreter._
+import sttp.tapir.server.vertx.cats.VertxCatsServerInterpreter.*
-object App extends IOApp {
+object App extends IOApp:
val responseEndpoint: PublicEndpoint[String, Unit, String, Any] =
endpoint
.in("response")
@@ -95,7 +92,7 @@ object App extends IOApp {
def handler(req: String): IO[Either[Unit, String]] =
IO.pure(Right(req))
- override def run(args: List[String]): IO[ExitCode] = {
+ override def run(args: List[String]): IO[ExitCode] =
Dispatcher[IO]
.flatMap { dispatcher =>
Resource
@@ -113,18 +110,16 @@ object App extends IOApp {
})
}
.use(_ => IO.never)
- }
-}
```
This interpreter also supports streaming using FS2 streams:
```scala mdoc:compile-only
-import cats.effect._
+import cats.effect.*
import cats.effect.std.Dispatcher
-import fs2._
+import fs2.*
import sttp.capabilities.fs2.Fs2Streams
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.vertx.cats.VertxCatsServerInterpreter
val streamedResponse =
@@ -152,7 +147,7 @@ to use this interpreter with ZIO.
Then import the object:
```scala mdoc:compile-only
-import sttp.tapir.server.vertx.zio.VertxZioServerInterpreter._
+import sttp.tapir.server.vertx.zio.VertxZioServerInterpreter.*
```
This object contains method `def route(e: ServerEndpoint[ZioStreams, RIO[R, *]])` which returns a function `Router => Route` that will create a route matching the endpoint definition, and with the logic attached as a handler.
@@ -163,12 +158,12 @@ Here is simple example which starts HTTP server with one route:
import io.vertx.core.Vertx
import io.vertx.ext.web.Router
import sttp.tapir.{plainBody, query}
-import sttp.tapir.ztapir._
+import sttp.tapir.ztapir.*
import sttp.tapir.server.vertx.zio.VertxZioServerInterpreter
-import sttp.tapir.server.vertx.zio.VertxZioServerInterpreter._
-import zio._
+import sttp.tapir.server.vertx.zio.VertxZioServerInterpreter.*
+import zio.*
-object Short extends ZIOAppDefault {
+object Short extends ZIOAppDefault:
override implicit val runtime = zio.Runtime.default
val responseEndpoint =
@@ -179,7 +174,7 @@ object Short extends ZIOAppDefault {
val attach = VertxZioServerInterpreter().route(responseEndpoint.zServerLogic { key => ZIO.succeed(key) })
- override def run = {
+ override def run =
ZIO.scoped(
ZIO
.acquireRelease(
@@ -196,8 +191,6 @@ object Short extends ZIOAppDefault {
ZIO.attempt(server.close()).flatMap(_.asRIO).orDie
} *> ZIO.never
)
- }
-}
```
This interpreter supports streaming using ZStreams.
diff --git a/doc/server/zio-http4s.md b/doc/server/zio-http4s.md
index c4b6d4769a..ceca6dc951 100644
--- a/doc/server/zio-http4s.md
+++ b/doc/server/zio-http4s.md
@@ -17,17 +17,17 @@ or just add the zio-http4s integration which already depends on `tapir-zio`:
"com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio" % "@VERSION@"
```
-Next, instead of the usual `import sttp.tapir._`, you should import (or extend the `ZTapir` trait, see [MyTapir](../mytapir.md)):
+Next, instead of the usual `import sttp.tapir.*`, you should import (or extend the `ZTapir` trait, see [MyTapir](../mytapir.md)):
```scala mdoc:compile-only
-import sttp.tapir.ztapir._
+import sttp.tapir.ztapir.*
```
This brings into scope all of the [basic](../endpoint/basics.md) input/output descriptions, which can be used to define an endpoint.
```{note}
You should have only one of these imports in your source file. Otherwise, you'll get naming conflicts. The
-`import sttp.tapir.ztapir._` import is meant as a complete replacement of `import sttp.tapir._`.
+`import sttp.tapir.ztapir.*` import is meant as a complete replacement of `import sttp.tapir.*`.
```
## Server logic
@@ -68,7 +68,7 @@ so that it is uniform across all endpoints, using the `.widen` method:
```scala mdoc:compile-only
import org.http4s.HttpRoutes
-import sttp.tapir.ztapir._
+import sttp.tapir.ztapir.*
import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter
import zio.RIO
@@ -118,7 +118,7 @@ method. This can then be added to a server builder using `withHttpWebSocketApp`,
import sttp.capabilities.WebSockets
import sttp.capabilities.zio.ZioStreams
import sttp.tapir.{CodecFormat, PublicEndpoint}
-import sttp.tapir.ztapir._
+import sttp.tapir.ztapir.*
import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter
import org.http4s.HttpRoutes
import org.http4s.blaze.server.BlazeServerBuilder
@@ -126,12 +126,12 @@ import org.http4s.server.Router
import org.http4s.server.websocket.WebSocketBuilder2
import scala.concurrent.ExecutionContext
import zio.{Task, Runtime, ZIO}
-import zio.interop.catz._
+import zio.interop.catz.*
import zio.stream.Stream
def runtime: Runtime[Any] = ??? // provided by ZIOAppDefault
-implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
+given ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
val wsEndpoint: PublicEndpoint[Unit, Unit, Stream[Throwable, String] => Stream[Throwable, String], ZioStreams with WebSockets] =
endpoint.get.in("count").out(webSocketBody[String, CodecFormat.TextPlain, String, CodecFormat.TextPlain](ZioStreams))
@@ -162,7 +162,7 @@ import sttp.capabilities.zio.ZioStreams
import sttp.model.sse.ServerSentEvent
import sttp.tapir.server.http4s.ztapir.{ZHttp4sServerInterpreter, serverSentEventsBody}
import sttp.tapir.PublicEndpoint
-import sttp.tapir.ztapir._
+import sttp.tapir.ztapir.*
import org.http4s.HttpRoutes
import zio.{Task, ZIO}
import zio.stream.{Stream, ZStream}
diff --git a/doc/server/ziohttp.md b/doc/server/ziohttp.md
index 256e82b345..6fce08f20e 100644
--- a/doc/server/ziohttp.md
+++ b/doc/server/ziohttp.md
@@ -17,17 +17,17 @@ or just add the zio-http integration which already depends on `tapir-zio`:
"com.softwaremill.sttp.tapir" %% "tapir-zio-http-server" % "@VERSION@"
```
-Next, instead of the usual `import sttp.tapir._`, you should import (or extend the `ZTapir` trait, see [MyTapir](../mytapir.md)):
+Next, instead of the usual `import sttp.tapir.*`, you should import (or extend the `ZTapir` trait, see [MyTapir](../mytapir.md)):
```scala mdoc:compile-only
-import sttp.tapir.ztapir._
+import sttp.tapir.ztapir.*
```
This brings into scope all the [basic](../endpoint/basics.md) input/output descriptions, which can be used to define an endpoint.
```{note}
You should have only one of these imports in your source file. Otherwise, you'll get naming conflicts. The
-`import sttp.tapir.ztapir._` import is meant as a complete replacement of `import sttp.tapir._`.
+`import sttp.tapir.ztapir.*` import is meant as a complete replacement of `import sttp.tapir.*`.
```
## Exposing endpoints
@@ -41,10 +41,10 @@ example:
```scala mdoc:compile-only
import sttp.tapir.PublicEndpoint
-import sttp.tapir.ztapir._
+import sttp.tapir.ztapir.*
import sttp.tapir.server.ziohttp.ZioHttpInterpreter
import zio.http.{Request, Response, Routes}
-import zio._
+import zio.*
def countCharacters(s: String): ZIO[Any, Nothing, Int] =
ZIO.succeed(s.length)
diff --git a/doc/testing.md b/doc/testing.md
index 8ac8d8996a..01502e01cb 100644
--- a/doc/testing.md
+++ b/doc/testing.md
@@ -26,10 +26,10 @@ dependency:
"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "@VERSION@"
```
-Let's assume you are using the [akka http](server/akkahttp.md) interpreter. Given the following server endpoint:
+Let's assume you are using the [pekko http](server/pekkohttp.md) interpreter. Given the following server endpoint:
```scala mdoc:silent
-import sttp.tapir._
+import sttp.tapir.*
import sttp.tapir.server.ServerEndpoint
import scala.concurrent.Future
@@ -53,11 +53,11 @@ A test which verifies how this endpoint behaves when interpreter as a server mig
```scala mdoc:silent
import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.should.Matchers
-import sttp.client3._
+import sttp.client3.*
import sttp.client3.testing.SttpBackendStub
import sttp.tapir.server.stub.TapirStubInterpreter
-class MySpec extends AsyncFlatSpec with Matchers {
+class MySpec extends AsyncFlatSpec with Matchers:
it should "work" in {
// given
val backendStub: SttpBackend[Future, Any] = TapirStubInterpreter(SttpBackendStub.asynchronousFuture)
@@ -74,7 +74,6 @@ class MySpec extends AsyncFlatSpec with Matchers {
// then
response.map(_.body shouldBe Right("hello user123"))
}
-}
```
The `.backend` method creates the enriched `SttpBackendStub`, using the provided server endpoints and their
@@ -85,12 +84,12 @@ Projects generated using [adopt-tapir](https://adopt-tapir.softwaremill.com) inc
### Custom interpreters
Custom interpreters can be provided to the stub. For example, to test custom exception handling, we might have the
-following customised akka http options:
+following customised pekko http options:
```scala mdoc:silent
import sttp.tapir.server.interceptor.exception.ExceptionHandler
import sttp.tapir.server.interceptor.CustomiseInterceptors
-import sttp.tapir.server.akkahttp.AkkaHttpServerOptions
+import sttp.tapir.server.pekkohttp.PekkoHttpServerOptions
import sttp.tapir.server.model.ValuedEndpointOutput
import sttp.model.StatusCode
@@ -101,9 +100,9 @@ val exceptionHandler = ExceptionHandler.pure[Future](ctx =>
))
)
-val customOptions: CustomiseInterceptors[Future, AkkaHttpServerOptions] = {
+val customOptions: CustomiseInterceptors[Future, PekkoHttpServerOptions] = {
import scala.concurrent.ExecutionContext.Implicits.global
- AkkaHttpServerOptions.customiseInterceptors
+ PekkoHttpServerOptions.customiseInterceptors
.exceptionHandler(exceptionHandler)
}
```
@@ -111,8 +110,7 @@ val customOptions: CustomiseInterceptors[Future, AkkaHttpServerOptions] = {
Testing such an interceptor requires simulating an exception being thrown in the server logic:
```scala mdoc:silent
-class MySpec2 extends AsyncFlatSpec with Matchers {
-
+class MySpec2 extends AsyncFlatSpec with Matchers:
it should "use my custom exception handler" in {
// given
val stub = TapirStubInterpreter(customOptions, SttpBackendStub.asynchronousFuture)
@@ -127,7 +125,6 @@ class MySpec2 extends AsyncFlatSpec with Matchers {
// then
.map(_.body shouldBe Left("failed due to error"))
}
-}
```
Note that to provide alternate success/error outputs given a `ServerEndpoint`, the endpoint will have to be typed
@@ -149,16 +146,16 @@ And the following imports:
```scala mdoc:silent
import sttp.client3.testing.SttpBackendStub
-import sttp.tapir.server.stub._
+import sttp.tapir.server.stub.*
```
Then, given the following endpoint:
```scala mdoc:silent
-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 io.circe.generic.auto.*
case class ResponseWrapper(value: Double)
@@ -203,16 +200,16 @@ Add the following dependency:
Imports:
```scala mdoc:silent
-import sttp.tapir.server.mockserver._
+import sttp.tapir.server.mockserver.*
```
Then, given the following endpoint:
```scala mdoc:silent
-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 io.circe.generic.auto.*
case class SampleIn(name: String, age: Int)
@@ -356,4 +353,4 @@ Results in:
```scala mdoc
result2.toString
-```
\ No newline at end of file
+```
diff --git a/examples2/src/main/resources/webapp/img/logo.png b/examples/src/main/resources/webapp/img/logo.png
similarity index 100%
rename from examples2/src/main/resources/webapp/img/logo.png
rename to examples/src/main/resources/webapp/img/logo.png
diff --git a/examples2/src/main/resources/webapp/index.html b/examples/src/main/resources/webapp/index.html
similarity index 100%
rename from examples2/src/main/resources/webapp/index.html
rename to examples/src/main/resources/webapp/index.html
diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala
index 1dac16a447..a6b444ccf7 100644
--- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala
@@ -12,7 +12,7 @@ import sttp.tapir.server.http4s.Http4sServerInterpreter
import scala.concurrent.ExecutionContext
-object HelloWorldHttp4sServer extends IOApp {
+object HelloWorldHttp4sServer extends IOApp:
// the endpoint: single fixed path input ("hello"), single query parameter
// corresponds to: GET /hello?name=...
val helloWorld: PublicEndpoint[String, Unit, String, Any] =
@@ -24,7 +24,7 @@ object HelloWorldHttp4sServer extends IOApp {
implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
- override def run(args: List[String]): IO[ExitCode] = {
+ override def run(args: List[String]): IO[ExitCode] =
// starting the server
BlazeServerBuilder[IO]
.withExecutionContext(ec)
@@ -40,5 +40,3 @@ object HelloWorldHttp4sServer extends IOApp {
}
}
.as(ExitCode.Success)
- }
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala
index eaf606a3b4..0ba70e423c 100644
--- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala
@@ -7,7 +7,7 @@ import sttp.shared.Identity
import sttp.tapir.server.netty.cats.NettyCatsServer
import sttp.tapir.*
-object HelloWorldNettyCatsServer extends IOApp.Simple {
+object HelloWorldNettyCatsServer extends IOApp.Simple:
// One endpoint on GET /hello with query parameter `name`
val helloWorldEndpoint: PublicEndpoint[String, Unit, String, Any] =
endpoint.get.in("hello").in(query[String]("name")).out(stringBody)
@@ -53,4 +53,3 @@ object HelloWorldNettyCatsServer extends IOApp.Simple {
.guarantee(binding.stop())
} yield result
}
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala
index a70a84710f..53c6bbf1ca 100644
--- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala
@@ -9,7 +9,7 @@ import zio.*
import zio.http.{Response => ZioHttpResponse, Routes, Server}
import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder}
-object HelloWorldZioHttpServer extends ZIOAppDefault {
+object HelloWorldZioHttpServer extends ZIOAppDefault:
// a simple string-only endpoint
val helloWorld: PublicEndpoint[String, Unit, String, Any] =
endpoint.get
@@ -44,4 +44,3 @@ object HelloWorldZioHttpServer extends ZIOAppDefault {
Server.live
)
.exitCode
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala
index 74df29d364..35af2d0e8f 100644
--- a/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala
@@ -12,9 +12,9 @@ import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter
import sttp.tapir.swagger.bundle.SwaggerInterpreter
import sttp.tapir.ztapir.*
import zio.interop.catz.*
-import zio.{Console, IO, Layer, RIO, ZIO, ZIOAppDefault, ZLayer}
+import zio.{Console, ExitCode, IO, Layer, RIO, URIO, ZIO, ZIOAppDefault, ZLayer}
-object ZioEnvExampleHttp4sServer extends ZIOAppDefault {
+object ZioEnvExampleHttp4sServer extends ZIOAppDefault:
// Domain classes, services, layers
case class Pet(species: String, url: String)
@@ -73,6 +73,4 @@ object ZioEnvExampleHttp4sServer extends ZIOAppDefault {
}
- override def run =
- serve.provide(PetService.live).exitCode
-}
+ override def run: URIO[Any, ExitCode] = serve.provide(PetService.live).exitCode
diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala
index 05e157ae58..4ae638389f 100644
--- a/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala
@@ -14,7 +14,7 @@ import sttp.tapir.ztapir.*
import zio.interop.catz.*
import zio.{ExitCode, Task, URIO, ZIO, ZIOAppDefault}
-object ZioExampleHttp4sServer extends ZIOAppDefault {
+object ZioExampleHttp4sServer extends ZIOAppDefault:
case class Pet(species: String, url: String)
// Sample endpoint, with the logic implemented directly using .toRoutes
@@ -61,4 +61,3 @@ object ZioExampleHttp4sServer extends ZIOAppDefault {
)
override def run: URIO[Any, ExitCode] = serve.exitCode
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala
index 3bb2ba9d82..f3d49a2264 100644
--- a/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala
@@ -10,7 +10,7 @@ import sttp.tapir.ztapir.*
import zio.http.{Response => ZioHttpResponse, Routes, Server}
import zio.{ExitCode, Task, URIO, ZIO, ZIOAppDefault, ZLayer}
-object ZioExampleZioHttpServer extends ZIOAppDefault {
+object ZioExampleZioHttpServer extends ZIOAppDefault:
case class Pet(species: String, url: String)
// Sample endpoint, with the logic implemented directly using .toRoutes
@@ -48,4 +48,3 @@ object ZioExampleZioHttpServer extends ZIOAppDefault {
Server.live
)
.exitCode
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala b/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala
index 1260aeb15e..02618e483b 100644
--- a/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala
@@ -12,7 +12,7 @@ import zio.Console.printLine
import zio.*
import zio.interop.catz.*
-object ZioPartialServerLogicHttp4s extends ZIOAppDefault {
+object ZioPartialServerLogicHttp4s extends ZIOAppDefault:
def greet(user: User, salutation: String): ZIO[Any, Nothing, String] = {
val greeting = s"$salutation, ${user.name}!"
@@ -79,18 +79,17 @@ object ZioPartialServerLogicHttp4s extends ZIOAppDefault {
.provide(UserService.live)
.exitCode
)
-}
+end ZioPartialServerLogicHttp4s
-object UserAuthenticationLayer {
+object UserAuthenticationLayer:
type UserService = UserService.Service
case class User(name: String)
val AuthenticationErrorCode = 1001
- object UserService {
- trait Service {
+ object UserService:
+ trait Service:
def auth(token: String): IO[Int, User]
- }
val live: ZLayer[Any, Nothing, Service] = ZLayer.succeed(new Service {
def auth(token: String): IO[Int, User] = {
@@ -100,6 +99,4 @@ object UserAuthenticationLayer {
})
def auth(token: String): ZIO[UserService, Int, User] = ZIO.environmentWithZIO(_.get.auth(token))
- }
-
-}
+ end UserService
diff --git a/examples/src/main/scala/sttp/tapir/examples/BooksExample.scala b/examples/src/main/scala/sttp/tapir/examples/booksExample.scala
similarity index 91%
rename from examples/src/main/scala/sttp/tapir/examples/BooksExample.scala
rename to examples/src/main/scala/sttp/tapir/examples/booksExample.scala
index 9a588d0683..77ff388654 100644
--- a/examples/src/main/scala/sttp/tapir/examples/BooksExample.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/booksExample.scala
@@ -1,9 +1,11 @@
package sttp.tapir.examples
-import sttp.tapir.examples.logging.Logging
import sttp.tapir.generic.auto.*
-object BooksExample extends App with Logging {
+@main def booksExample(): Unit =
+ import org.slf4j.{Logger, LoggerFactory}
+ val logger: Logger = LoggerFactory.getLogger(getClass().getName)
+
type Limit = Option[Int]
type AuthToken = String
@@ -13,9 +15,8 @@ object BooksExample extends App with Logging {
case class Book(title: String, genre: Genre, year: Int, author: Author)
case class BooksQuery(genre: Option[String], limit: Limit)
- /** Descriptions of endpoints used in the example.
- */
- object Endpoints {
+ /** Descriptions of endpoints used in the example. */
+ object Endpoints:
import io.circe.generic.auto.*
import sttp.tapir.*
import sttp.tapir.json.circe.*
@@ -44,11 +45,11 @@ object BooksExample extends App with Logging {
val booksListingByGenre: PublicEndpoint[BooksQuery, String, Vector[Book], Any] = baseEndpoint.get
.in(("list" / path[String]("genre").map(Option(_))(_.get)).and(limitParameter).mapTo[BooksQuery])
.out(jsonBody[Vector[Book]])
- }
+ end Endpoints
//
- object Library {
+ object Library:
import java.util.concurrent.atomic.AtomicReference
val Books = new AtomicReference(
@@ -67,7 +68,7 @@ object BooksExample extends App with Logging {
)
)
- def getBooks(query: BooksQuery): Vector[Book] = {
+ def getBooks(query: BooksQuery): Vector[Book] =
val allBooks = Books.get()
val limitedBooks = query.limit match {
case None => allBooks
@@ -78,8 +79,7 @@ object BooksExample extends App with Logging {
case Some(g) => limitedBooks.filter(_.genre.name.equalsIgnoreCase(g))
}
filteredBooks
- }
- }
+ end Library
//
@@ -87,7 +87,7 @@ object BooksExample extends App with Logging {
import sttp.tapir.server.ServerEndpoint
import scala.concurrent.Future
- def booksServerEndpoints: List[ServerEndpoint[Any, Future]] = {
+ def booksServerEndpoints: List[ServerEndpoint[Any, Future]] =
import scala.concurrent.ExecutionContext.Implicits.global
def bookAddLogic(book: Book, token: AuthToken): Future[Either[String, Unit]] =
@@ -119,17 +119,17 @@ object BooksExample extends App with Logging {
booksListing.serverLogic(bookListingLogic),
booksListingByGenre.serverLogic(bookListingByGenreLogic)
)
- }
+ end booksServerEndpoints
- def swaggerUIServerEndpoints: List[ServerEndpoint[Any, Future]] = {
+ def swaggerUIServerEndpoints: List[ServerEndpoint[Any, Future]] =
import sttp.tapir.swagger.bundle.SwaggerInterpreter
// interpreting the endpoint descriptions as yaml openapi documentation
// exposing the docs using SwaggerUI endpoints, interpreted as an pekko-http route
SwaggerInterpreter().fromEndpoints(List(addBook, booksListing, booksListingByGenre), "The Tapir Library", "1.0")
- }
+ end swaggerUIServerEndpoints
- def startServer(serverEndpoints: List[ServerEndpoint[Any, Future]]): Unit = {
+ def startServer(serverEndpoints: List[ServerEndpoint[Any, Future]]): Unit =
import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.http.scaladsl.Http
@@ -144,9 +144,9 @@ object BooksExample extends App with Logging {
Await.result(Http().newServerAt("localhost", 8080).bindFlow(routes), 1.minute)
logger.info("Server started")
- }
+ end startServer
- def makeClientRequest(): Unit = {
+ def makeClientRequest(): Unit =
import sttp.client3.*
import sttp.tapir.client.sttp.SttpClientInterpreter
@@ -155,7 +155,7 @@ object BooksExample extends App with Logging {
val result: Either[String, Vector[Book]] = client(Some(3))
logger.info("Result of listing request with limit 3: " + result)
- }
+ end makeClientRequest
logger.info("Welcome to the Tapir Library example!")
@@ -166,4 +166,3 @@ object BooksExample extends App with Logging {
makeClientRequest()
logger.info("Try out the API by opening the Swagger UI: http://localhost:8080/docs")
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/BooksPicklerExample.scala b/examples/src/main/scala/sttp/tapir/examples/booksPicklerExample.scala
similarity index 92%
rename from examples/src/main/scala/sttp/tapir/examples/BooksPicklerExample.scala
rename to examples/src/main/scala/sttp/tapir/examples/booksPicklerExample.scala
index b2cdc29ed9..055c4375ed 100644
--- a/examples/src/main/scala/sttp/tapir/examples/BooksPicklerExample.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/booksPicklerExample.scala
@@ -1,12 +1,14 @@
package sttp.tapir.examples
-import sttp.tapir.examples.logging.Logging
import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding}
import scala.concurrent.Await
import scala.concurrent.duration.Duration
-object BooksPicklerExample extends App with Logging {
+@main def booksPicklerExample(): Unit =
+ import org.slf4j.{Logger, LoggerFactory}
+ val logger: Logger = LoggerFactory.getLogger(getClass.getName)
+
type Limit = Option[Int]
type AuthToken = String
@@ -19,9 +21,8 @@ object BooksPicklerExample extends App with Logging {
val declaredPort = 9090
val declaredHost = "localhost"
- /** Descriptions of endpoints used in the example.
- */
- object Endpoints {
+ /** Descriptions of endpoints used in the example. */
+ object Endpoints:
import sttp.tapir.*
import sttp.tapir.json.pickler.*
import sttp.tapir.json.pickler.generic.auto.*
@@ -50,10 +51,11 @@ object BooksPicklerExample extends App with Logging {
val booksListingByGenre: PublicEndpoint[BooksQuery, String, Vector[Book], Any] = baseEndpoint.get
.in(("list" / path[String]("genre").map(Option(_))(_.get)).and(limitParameter).mapTo[BooksQuery])
.out(jsonBody[Vector[Book]])
- }
+ end Endpoints
+
//
- object Library {
+ object Library:
import java.util.concurrent.atomic.AtomicReference
val Books = new AtomicReference(
@@ -84,7 +86,7 @@ object BooksPicklerExample extends App with Logging {
}
filteredBooks
}
- }
+ end Library
//
@@ -93,8 +95,7 @@ object BooksPicklerExample extends App with Logging {
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
- def booksServerEndpoints: List[ServerEndpoint[Any, Future]] = {
-
+ def booksServerEndpoints: List[ServerEndpoint[Any, Future]] =
def bookAddLogic(book: Book, token: AuthToken): Future[Either[String, Unit]] =
Future {
if (token != "secret") {
@@ -124,24 +125,24 @@ object BooksPicklerExample extends App with Logging {
booksListing.serverLogic(bookListingLogic),
booksListingByGenre.serverLogic(bookListingByGenreLogic)
)
- }
+ end booksServerEndpoints
- def swaggerUIServerEndpoints: List[ServerEndpoint[Any, Future]] = {
+ def swaggerUIServerEndpoints: List[ServerEndpoint[Any, Future]] =
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
SwaggerInterpreter().fromEndpoints(List(addBook), "The Tapir Library", "1.0")
- }
+ end swaggerUIServerEndpoints
- def makeClientRequest(): Unit = {
+ def makeClientRequest(): Unit =
import sttp.client3.*
import sttp.tapir.client.sttp.SttpClientInterpreter
val client = SttpClientInterpreter().toQuickClient(booksListing, Some(uri"http://$declaredHost:$declaredPort"))
val result: Either[String, Vector[Book]] = client(Some(3))
logger.info("Result of listing request with limit 3: " + result)
- }
+ end makeClientRequest
logger.info("Welcome to the Tapir Library example!")
@@ -170,4 +171,3 @@ object BooksPicklerExample extends App with Logging {
logger.info("Press ENTER to stop the server...")
scala.io.StdIn.readLine
Await.result(serverBinding.stop(), Duration.Inf)
-}
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 096886adc5..076dbd3041 100644
--- a/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala
@@ -1,14 +1,16 @@
package sttp.tapir.examples.client
import cats.effect.{ExitCode, IO, IOApp}
-import sttp.tapir.examples.logging.Logging
import io.circe.generic.auto.*
import sttp.tapir.*
import sttp.tapir.client.http4s.Http4sClientInterpreter
import sttp.tapir.generic.auto.*
import sttp.tapir.json.circe.*
-object Http4sClientExample extends IOApp with Logging {
+import org.slf4j.{Logger, LoggerFactory}
+val logger: Logger = LoggerFactory.getLogger(getClass.getName)
+
+object Http4sClientExample extends IOApp:
case class User(id: Int, name: String)
@@ -19,7 +21,7 @@ object Http4sClientExample extends IOApp with Logging {
.out(jsonBody[User])
// Define http4s routes that will be used to test the request.
- private val http4sRoutes = {
+ private val http4sRoutes =
import io.circe.generic.auto.*
import io.circe.syntax.*
import org.http4s.*
@@ -32,9 +34,8 @@ object Http4sClientExample extends IOApp with Logging {
Ok(User(userId, "Joanna").asJson)
}
.orNotFound
- }
- override def run(args: List[String]): IO[ExitCode] = {
+ override def run(args: List[String]): IO[ExitCode] =
val userId = 5
// Interpret the endpoint as a request and a response parser.
@@ -51,5 +52,3 @@ object Http4sClientExample extends IOApp with Logging {
result <- parseResponse(response)
_ <- IO(logger.info(s"The result is: $result"))
} yield ExitCode.Success
- }
-}
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
deleted file mode 100644
index 904e37eaa3..0000000000
--- a/examples/src/main/scala/sttp/tapir/examples/custom_types/CommaSeparatedQueryParameter.scala
+++ /dev/null
@@ -1,80 +0,0 @@
-package sttp.tapir.examples.custom_types
-
-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 scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.duration.Duration
-import scala.concurrent.{Await, Future}
-
-/*
- A simple example that showcases how to use a custom Enumeration as a comma-separated query parameter.
-
- We're going to build a simple endpoint capable of accepting requests such as
- `GET tapirs?breeds=Malayan,CentralAmerican`
-
- Warning: are you using Scala 2.12 or older? Then you need to write your own `Delimited`, as the one provided
- unfortunately only works with Scala 2.13+.
- */
-object CommaSeparatedQueryParameter extends App {
-
- /*
- It's possible to use `CommaSeparated` with any type with a `Codec[String, T, CodecFormat.TextPlain]` instance.
-
- That means, for example
- - scala.Enumeration, as in this example
- - sealed families (ADTs)
- - scala 3 enums
- - `Enumeratum` enumerations
- - etc.
-
- That codec will also determine validation rules, e.g. if the input should be case-insensitive or not.
-
- See also:
- - https://tapir.softwaremill.com/en/latest/endpoint/enumerations.html
- - https://tapir.softwaremill.com/en/latest/endpoint/integrations.html#enumeratum-integration
- */
- object TapirBreeds extends Enumeration {
- type Breed = Value
-
- val CentralAmerican: Breed = Value("Central American")
- val SouthAmerican: Breed = Value("South American")
- val Mountain: Breed = Value("Mountain")
- val Malayan: Breed = Value("Malayan")
- }
-
- val echoTapirs: Endpoint[Unit, CommaSeparated[TapirBreeds.Breed], Unit, String, Any] = endpoint.get
- .in("tapirs")
- .in(
- query[CommaSeparated[TapirBreeds.Breed]]("breeds")
- // If we want the filter to be optional, we need either `default` or to make to wrap the parameter in `Optional`
- .default(Delimited[",", TapirBreeds.Breed](TapirBreeds.values.toList))
- )
- .out(stringBody)
-
- val echoTapirsServerEndpoint: ServerEndpoint[Any, Future] =
- echoTapirs.serverLogicSuccess[Future](breeds => Future.successful(s"Tapir breeds: ${breeds.values.mkString(", ")}"))
-
- ////////////////////////////////// Boilerplate: starting up server and swagger UI //////////////////////////////////
-
- val docsEndpoints = SwaggerInterpreter().fromServerEndpoints[Future](List(echoTapirsServerEndpoint), "Echo", "1.0.0")
-
- val serverBinding: NettyFutureServerBinding =
- Await.result(
- NettyFutureServer()
- .port(8080)
- .host("localhost")
- .addEndpoints(echoTapirsServerEndpoint :: docsEndpoints)
- .start(),
- Duration.Inf
- )
-
- println(s"Go to: http://${serverBinding.hostName}:${serverBinding.port}/docs")
- println("Press any key to exit ...")
- scala.io.StdIn.readLine()
-
- Await.result(serverBinding.stop(), Duration.Inf)
-}
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 c457a9e152..9a244fd469 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
@@ -6,7 +6,7 @@ import sttp.tapir.*
import sttp.tapir.generic.auto.*
import sttp.tapir.json.circe.*
-object EndpointWithCustomTypes {
+object EndpointWithCustomTypes:
// An over-complicated, example custom type
trait MyId {
def id: String
@@ -28,4 +28,3 @@ object EndpointWithCustomTypes {
implicit val myIdEncoder: Encoder[MyId] = Encoder.encodeString.contramap(_.id)
implicit val myIdDecoder: Decoder[MyId] = Decoder.decodeString.map(s => new MyIdImpl(s))
val endpointWithPerson: PublicEndpoint[Unit, Unit, Person, Nothing] = endpoint.out(jsonBody[Person])
-}
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
similarity index 93%
rename from examples/src/main/scala/sttp/tapir/examples/custom_types/BooksExampleSemiauto.scala
rename to examples/src/main/scala/sttp/tapir/examples/custom_types/booksExampleSemiauto.scala
index 909a3c6de9..ba442caeec 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
@@ -1,9 +1,11 @@
package sttp.tapir.examples.custom_types
-import sttp.tapir.examples.logging.Logging
import sttp.tapir.Schema
-object BooksExampleSemiauto extends App with Logging {
+@main def booksExampleSemiauto(): Unit =
+ import org.slf4j.{Logger, LoggerFactory}
+ val logger: Logger = LoggerFactory.getLogger(getClass().getName)
+
type Limit = Option[Int]
type AuthToken = String
@@ -21,7 +23,7 @@ object BooksExampleSemiauto extends App with Logging {
/** Descriptions of endpoints used in the example.
*/
- object Endpoints {
+ object Endpoints:
import io.circe.generic.auto.*
import sttp.tapir.*
import sttp.tapir.json.circe.*
@@ -50,11 +52,11 @@ object BooksExampleSemiauto extends App with Logging {
val booksListingByGenre: PublicEndpoint[BooksQuery, String, Vector[Book], Any] = baseEndpoint.get
.in(("list" / path[String]("genre").map(Option(_))(_.get)).and(limitParameter).mapTo[BooksQuery])
.out(jsonBody[Vector[Book]])
- }
+ end Endpoints
//
- object Library {
+ object Library:
import java.util.concurrent.atomic.AtomicReference
val Books = new AtomicReference(
@@ -85,7 +87,7 @@ object BooksExampleSemiauto extends App with Logging {
}
filteredBooks
}
- }
+ end Library
//
@@ -94,7 +96,7 @@ object BooksExampleSemiauto extends App with Logging {
import scala.concurrent.Future
- def booksServerEndpoints: List[ServerEndpoint[Any, Future]] = {
+ def booksServerEndpoints: List[ServerEndpoint[Any, Future]] =
import scala.concurrent.ExecutionContext.Implicits.global
def bookAddLogic(book: Book, token: AuthToken): Future[Either[String, Unit]] =
@@ -126,17 +128,17 @@ object BooksExampleSemiauto extends App with Logging {
booksListing.serverLogic(bookListingLogic),
booksListingByGenre.serverLogic(bookListingByGenreLogic)
)
- }
+ end booksServerEndpoints
- def swaggerUIServerEndpoints: List[ServerEndpoint[Any, Future]] = {
+ def swaggerUIServerEndpoints: List[ServerEndpoint[Any, Future]] =
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
SwaggerInterpreter().fromEndpoints(List(addBook, booksListing, booksListingByGenre), "The Tapir Library", "1.0")
- }
+ end swaggerUIServerEndpoints
- def startServer(serverEndpoints: List[ServerEndpoint[Any, Future]]): Unit = {
+ def startServer(serverEndpoints: List[ServerEndpoint[Any, Future]]): Unit =
import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.http.scaladsl.Http
import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter
@@ -150,9 +152,9 @@ object BooksExampleSemiauto extends App with Logging {
Await.result(Http().newServerAt("localhost", 8080).bindFlow(routes), 1.minute)
logger.info("Server started")
- }
+ end startServer
- def makeClientRequest(): Unit = {
+ def makeClientRequest(): Unit =
import sttp.client3.*
import sttp.tapir.client.sttp.SttpClientInterpreter
@@ -161,7 +163,7 @@ object BooksExampleSemiauto extends App with Logging {
val result: Either[String, Vector[Book]] = client(Some(3))
logger.info("Result of listing request with limit 3: " + result)
- }
+ end makeClientRequest
logger.info("Welcome to the Tapir Library example!")
@@ -172,4 +174,3 @@ object BooksExampleSemiauto extends App with Logging {
makeClientRequest()
logger.info("Try out the API by opening the Swagger UI: http://localhost:8080/docs")
-}
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
new file mode 100644
index 0000000000..1a87bfea52
--- /dev/null
+++ b/examples/src/main/scala/sttp/tapir/examples/custom_types/commaSeparatedQueryParameter.scala
@@ -0,0 +1,73 @@
+package sttp.tapir.examples.custom_types
+
+import ox.supervised
+import sttp.shared.Identity
+import sttp.tapir.*
+import sttp.tapir.CodecFormat.TextPlain
+import sttp.tapir.model.{CommaSeparated, Delimited}
+import sttp.tapir.server.netty.sync.NettySyncServer
+import sttp.tapir.swagger.bundle.SwaggerInterpreter
+
+enum TapirBreeds(val name: String):
+ case CentralAmerican extends TapirBreeds("Central American")
+ case SouthAmerican extends TapirBreeds("South American")
+ case Mountain extends TapirBreeds("Mountain")
+ case Malayan extends TapirBreeds("Malayan")
+
+ override def toString: String = name
+
+/*
+ A simple example that showcases how to use a custom `enum` as a comma-separated query parameter.
+
+ We're going to build a simple endpoint capable of accepting requests such as
+ `GET tapirs?breeds=Malayan,CentralAmerican`
+ */
+@main def commaSeparatedQueryParameter(): Unit =
+ /*
+ It's possible to use `CommaSeparated` with any type with a `Codec[String, T, CodecFormat.TextPlain]` instance.
+
+ That means, for example
+ - scala.Enumeration, as in this example
+ - sealed families (ADTs)
+ - scala 3 enums
+ - `Enumeratum` enumerations
+ - etc.
+
+ That codec will also determine validation rules, e.g. if the input should be case-insensitive or not.
+
+ See also:
+ - https://tapir.softwaremill.com/en/latest/endpoint/enumerations.html
+ - https://tapir.softwaremill.com/en/latest/endpoint/integrations.html#enumeratum-integration
+ */
+
+ given Codec[String, TapirBreeds, TextPlain] = Codec.derivedEnumeration[String, TapirBreeds].defaultStringBased
+
+ val echoTapirs: Endpoint[Unit, CommaSeparated[TapirBreeds], Unit, String, Any] = endpoint.get
+ .in("tapirs")
+ .in(
+ query[CommaSeparated[TapirBreeds]]("breeds")
+ // If we want the filter to be optional, we need either `default` or to make to wrap the parameter in `Optional`
+ .default(Delimited[",", TapirBreeds](TapirBreeds.values.toList))
+ )
+ .out(stringBody)
+
+ val echoTapirsServerEndpoint =
+ echoTapirs.handleSuccess(breeds => s"Tapir breeds: ${breeds.values.mkString(", ")}")
+
+ ////////////////////////////////// Boilerplate: starting up server and swagger UI //////////////////////////////////
+
+ val docsEndpoints = SwaggerInterpreter().fromServerEndpoints[Identity](List(echoTapirsServerEndpoint), "Echo", "1.0.0")
+
+ supervised {
+ val binding = NettySyncServer()
+ .port(8080)
+ .host("localhost")
+ .addEndpoints(echoTapirsServerEndpoint :: docsEndpoints)
+ .start()
+
+ println(s"Go to: http://${binding.hostName}:${binding.port}/docs")
+ println("Press any key to exit ...")
+ scala.io.StdIn.readLine()
+
+ binding.stop()
+ }
diff --git a/examples/src/main/scala/sttp/tapir/examples/custom_types/sealedTraitWithDiscriminator.scala b/examples/src/main/scala/sttp/tapir/examples/custom_types/sealedTraitWithDiscriminator.scala
new file mode 100644
index 0000000000..9ac37e6386
--- /dev/null
+++ b/examples/src/main/scala/sttp/tapir/examples/custom_types/sealedTraitWithDiscriminator.scala
@@ -0,0 +1,52 @@
+package sttp.tapir.examples.custom_types
+
+import io.circe.Codec as CirceCodec
+import io.circe.derivation.Configuration as CirceConfiguration
+import ox.supervised
+import sttp.shared.Identity
+import sttp.tapir.*
+import sttp.tapir.generic.Configuration
+import sttp.tapir.json.circe.*
+import sttp.tapir.server.netty.sync.NettySyncServer
+import sttp.tapir.swagger.bundle.SwaggerInterpreter
+
+@main def sealedTraitWithDiscriminator(): Unit =
+ // data structures
+ sealed trait Node
+ case class Leaf(value: String) extends Node
+ case class Branch(name: String, children: Seq[Node]) extends Node
+
+ val discriminatorFieldName = "kind"
+
+ // these configs must match: one configures tapir's schema, the other circe's encoding/decoding to/from json
+ given Configuration = Configuration.default.withDiscriminator(discriminatorFieldName)
+ // the CirceConfiguration class is a renamed import (see above), as the name would clash with tapir's Configuration
+ given CirceConfiguration = CirceConfiguration.default.withDiscriminator(discriminatorFieldName)
+
+ given CirceCodec[Node] = CirceCodec.AsObject.derivedConfigured
+ given Schema[Node] = Schema.derived
+
+ // endpoint description; the configs are used when deriving the Schema, Encoder and Decoder, which are implicit
+ // parameters to jsonBody[Node]
+ val nodesListing: PublicEndpoint[Unit, Unit, Node, Any] = endpoint.get
+ .in("nodes")
+ .out(jsonBody[Node])
+
+ val nodesListingServerEndpoint =
+ nodesListing.handleSuccess(_ => Branch("b", List(Leaf("x"), Leaf("y"))))
+
+ val docsEndpoints = SwaggerInterpreter().fromServerEndpoints[Identity](List(nodesListingServerEndpoint), "Nodes", "1.0.0")
+
+ supervised {
+ val binding = NettySyncServer()
+ .port(8080)
+ .host("localhost")
+ .addEndpoints(nodesListingServerEndpoint :: docsEndpoints)
+ .start()
+
+ println(s"Go to: http://${binding.hostName}:${binding.port}/docs")
+ println("Press any key to exit ...")
+ scala.io.StdIn.readLine()
+
+ binding.stop()
+ }
diff --git a/examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala b/examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala
index 7889a86572..2a97c02f58 100644
--- a/examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala
@@ -24,7 +24,7 @@ import sttp.tapir.json.circe.*
import sttp.tapir.codec.iron.given
import sttp.tapir.generic.auto.*
-object IronRefinementErrorsNettyServer extends IOApp.Simple {
+object IronRefinementErrorsNettyServer extends IOApp.Simple:
case class IronException(error: String) extends Exception(error)
@@ -81,7 +81,7 @@ object IronRefinementErrorsNettyServer extends IOApp.Simple {
private val declaredPort = 9090
private val declaredHost = "localhost"
- override def run = NettyCatsServer
+ override def run: IO[Unit] = NettyCatsServer
.io()
.use { server =>
// Don't forget to add the interceptor to server options
@@ -123,4 +123,3 @@ object IronRefinementErrorsNettyServer extends IOApp.Simple {
.guarantee(binding.stop())
} yield result
}
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/errors/CustomErrorsOnDecodeFailurePekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/errors/customErrorsOnDecodeFailurePekkoServer.scala
similarity index 96%
rename from examples/src/main/scala/sttp/tapir/examples/errors/CustomErrorsOnDecodeFailurePekkoServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/errors/customErrorsOnDecodeFailurePekkoServer.scala
index 4f41a1746c..07f86310b8 100644
--- a/examples/src/main/scala/sttp/tapir/examples/errors/CustomErrorsOnDecodeFailurePekkoServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/errors/customErrorsOnDecodeFailurePekkoServer.scala
@@ -3,6 +3,7 @@ package sttp.tapir.examples.errors
import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.http.scaladsl.Http
import org.apache.pekko.http.scaladsl.server.Route
+import ox.discard
import sttp.shared.Identity
import sttp.tapir.*
import sttp.tapir.server.pekkohttp.{PekkoHttpServerInterpreter, PekkoHttpServerOptions}
@@ -13,9 +14,7 @@ import sttp.client3.*
import sttp.monad.FutureMonad
import sttp.tapir.server.interceptor.decodefailure.{DecodeFailureHandler, DefaultDecodeFailureHandler}
-import scala.util.{Failure, Success}
-
-object CustomErrorsOnDecodeFailurePekkoServer extends App {
+@main def customErrorsOnDecodeFailurePekkoServer(): Unit =
implicit val actorSystem: ActorSystem = ActorSystem()
import actorSystem.dispatcher
@@ -63,5 +62,4 @@ object CustomErrorsOnDecodeFailurePekkoServer extends App {
binding
}
- Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute)
-}
+ Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute).discard
diff --git a/examples/src/main/scala/sttp/tapir/examples/errors/ErrorOutputsPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/errors/errorOutputsPekkoServer.scala
similarity index 95%
rename from examples/src/main/scala/sttp/tapir/examples/errors/ErrorOutputsPekkoServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/errors/errorOutputsPekkoServer.scala
index f4ebff48e4..7a0972c377 100644
--- a/examples/src/main/scala/sttp/tapir/examples/errors/ErrorOutputsPekkoServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/errors/errorOutputsPekkoServer.scala
@@ -3,6 +3,7 @@ package sttp.tapir.examples.errors
import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.http.scaladsl.Http
import org.apache.pekko.http.scaladsl.server.Route
+import ox.discard
import sttp.client3.*
import sttp.shared.Identity
import sttp.tapir.generic.auto.*
@@ -14,7 +15,7 @@ import io.circe.generic.auto.*
import scala.concurrent.duration.*
import scala.concurrent.{Await, Future}
-object ErrorOutputsPekkoServer extends App {
+@main def errorOutputsPekkoServer(): Unit =
implicit val actorSystem: ActorSystem = ActorSystem()
import actorSystem.dispatcher
@@ -49,5 +50,4 @@ object ErrorOutputsPekkoServer extends App {
binding
}
- Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute)
-}
+ Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute).discard
diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldArmeriaServer.scala b/examples/src/main/scala/sttp/tapir/examples/helloWorldArmeriaServer.scala
similarity index 96%
rename from examples/src/main/scala/sttp/tapir/examples/HelloWorldArmeriaServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/helloWorldArmeriaServer.scala
index 7e14d83549..bfadc347f5 100644
--- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldArmeriaServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/helloWorldArmeriaServer.scala
@@ -9,7 +9,7 @@ import sttp.tapir.*
import scala.concurrent.Future
-object HelloWorldArmeriaServer extends App {
+@main def helloWorldArmeriaServer(): Unit =
// the endpoint: single fixed path input ("hello"), single query parameter
// corresponds to: GET /hello?name=...
@@ -35,4 +35,3 @@ object HelloWorldArmeriaServer extends App {
assert(result == "Hello, Frodo!")
server.stop().join()
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldJdkHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/helloWorldJdkHttpServer.scala
similarity index 95%
rename from examples/src/main/scala/sttp/tapir/examples/HelloWorldJdkHttpServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/helloWorldJdkHttpServer.scala
index 4aa2a27205..d466dd7b6f 100644
--- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldJdkHttpServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/helloWorldJdkHttpServer.scala
@@ -6,8 +6,7 @@ import sttp.shared.Identity
import sttp.tapir.server.jdkhttp.*
import sttp.tapir.*
-object HelloWorldJdkHttpServer extends App {
-
+@main def helloWorldJdkHttpServer(): Unit =
// GET /hello endpoint, with query parameter `name`
val helloWorldEndpoint: PublicEndpoint[String, Unit, String, Any] =
endpoint.get.in("hello").in(query[String]("name")).out(stringBody)
@@ -20,8 +19,8 @@ object HelloWorldJdkHttpServer extends App {
val secondServerEndpoint = secondEndpoint.handleSuccess(_ => "IT WORKS!")
- private val declaredPort = 9090
- private val declaredHost = "localhost"
+ val declaredPort = 9090
+ val declaredHost = "localhost"
// Starting jdk http server
val server =
@@ -60,4 +59,3 @@ object HelloWorldJdkHttpServer extends App {
} finally {
server.stop(0)
}
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyFutureServer.scala b/examples/src/main/scala/sttp/tapir/examples/helloWorldNettyFutureServer.scala
similarity index 94%
rename from examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyFutureServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/helloWorldNettyFutureServer.scala
index 1f3be12861..efd8a53ac2 100644
--- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyFutureServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/helloWorldNettyFutureServer.scala
@@ -10,7 +10,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}
-object HelloWorldNettyFutureServer extends App {
+@main def helloWorldNettyFutureServer(): Unit =
// One endpoint on GET /hello with query parameter `name`
val helloWorldEndpoint: PublicEndpoint[String, Unit, String, Any] =
endpoint.get.in("hello").in(query[String]("name")).out(stringBody)
@@ -19,8 +19,9 @@ object HelloWorldNettyFutureServer extends App {
val helloWorldServerEndpoint = helloWorldEndpoint
.serverLogic(name => Future.successful[Either[Unit, String]](Right(s"Hello, $name!")))
- private val declaredPort = 9090
- private val declaredHost = "localhost"
+ val declaredPort = 9090
+ val declaredHost = "localhost"
+
// Starting netty server
val serverBinding: NettyFutureServerBinding =
Await.result(
@@ -53,4 +54,3 @@ object HelloWorldNettyFutureServer extends App {
assert(host == declaredHost, "Hosts don't match")
Await.result(serverBinding.stop(), Duration.Inf)
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettySyncServer.scala b/examples/src/main/scala/sttp/tapir/examples/helloWorldNettySyncServer.scala
similarity index 90%
rename from examples/src/main/scala/sttp/tapir/examples/HelloWorldNettySyncServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/helloWorldNettySyncServer.scala
index 0580ebaab2..537d176bd4 100644
--- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettySyncServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/helloWorldNettySyncServer.scala
@@ -4,7 +4,7 @@ import ox.*
import sttp.tapir.*
import sttp.tapir.server.netty.sync.NettySyncServer
-object HelloWorldNettySyncServer:
+@main def helloWorldNettySyncServer(): Unit =
val helloWorld = endpoint.get
.in("hello")
.in(query[String]("name"))
@@ -15,7 +15,7 @@ object HelloWorldNettySyncServer:
// Alternatively, if you need manual control of the structured concurrency scope, server lifecycle,
// or just metadata from `NettySyncServerBinding` (like port number), use `start()`:
-object HelloWorldNettySyncServer2:
+@main def helloWorldNettySyncServer2(): Unit =
val helloWorld = endpoint.get
.in("hello")
.in(query[String]("name"))
diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/helloWorldPekkoServer.scala
similarity index 97%
rename from examples/src/main/scala/sttp/tapir/examples/HelloWorldPekkoServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/helloWorldPekkoServer.scala
index 95f6ff5bed..b5fe8e1603 100644
--- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldPekkoServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/helloWorldPekkoServer.scala
@@ -11,7 +11,7 @@ import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter
import scala.concurrent.duration.*
import scala.concurrent.{Await, Future}
-object HelloWorldPekkoServer extends App {
+@main def helloWorldPekkoServer(): Unit =
implicit val actorSystem: ActorSystem = ActorSystem()
import actorSystem.dispatcher
@@ -37,4 +37,3 @@ object HelloWorldPekkoServer extends App {
}
Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute)
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/logging/Logging.scala b/examples/src/main/scala/sttp/tapir/examples/logging/Logging.scala
deleted file mode 100644
index fe33bf4dc4..0000000000
--- a/examples/src/main/scala/sttp/tapir/examples/logging/Logging.scala
+++ /dev/null
@@ -1,12 +0,0 @@
-package sttp.tapir.examples.logging
-
-import org.slf4j.{Logger, LoggerFactory}
-
-/** Defines a [[org.slf4j.Logger]] instance `logger` named according to the class into which this trait is mixed.
- *
- * In a real-life project, you might rather want to use a macros-based SLF4J wrapper or logging backend.
- */
-trait Logging {
-
- protected val logger: Logger = LoggerFactory.getLogger(getClass.getName)
-}
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 fdfa3a0adc..4d75eeac56 100644
--- a/examples/src/main/scala/sttp/tapir/examples/logging/ZioLoggingWithCorrelationIdNettyServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/logging/ZioLoggingWithCorrelationIdNettyServer.scala
@@ -9,7 +9,7 @@ import sttp.tapir.server.netty.zio.{NettyZioServer, NettyZioServerOptions}
import sttp.tapir.ztapir.*
import zio.{ExitCode, Task, URIO, ZIO, ZIOAppDefault, durationInt}
-object ZioLoggingWithCorrelationIdNettyServer extends ZIOAppDefault {
+object ZioLoggingWithCorrelationIdNettyServer extends ZIOAppDefault:
val CorrelationIdHeader = "X-Correlation-Id"
// An endpoint with some logging added
@@ -30,7 +30,7 @@ object ZioLoggingWithCorrelationIdNettyServer extends ZIOAppDefault {
}
})
- override def run: URIO[Any, ExitCode] = {
+ override def run: URIO[Any, ExitCode] =
val serverOptions = NettyZioServerOptions.customiseInterceptors.prependInterceptor(correlationIdInterceptor).options
(for {
binding <- NettyZioServer(serverOptions).port(8080).addEndpoint(loggingEndpoint).start()
@@ -39,5 +39,3 @@ object ZioLoggingWithCorrelationIdNettyServer extends ZIOAppDefault {
_ <- httpClient.send(basicRequest.get(uri"http://localhost:8080/hello?name=Bob"))
_ <- binding.stop()
} yield ()).exitCode
- }
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/multipart/multipartFormUploadPekkoServer.scala
similarity index 96%
rename from examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadPekkoServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/multipart/multipartFormUploadPekkoServer.scala
index d978daa7fd..424157c8d8 100644
--- a/examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadPekkoServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/multipart/multipartFormUploadPekkoServer.scala
@@ -5,6 +5,7 @@ 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 ox.discard
import sttp.client3.*
import sttp.shared.Identity
import sttp.tapir.generic.auto.*
@@ -15,7 +16,7 @@ import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter
import scala.concurrent.{Await, Future}
import scala.concurrent.duration.*
-object MultipartFormUploadPekkoServer extends App {
+@main def multipartFormUploadPekkoServer(): Unit =
implicit val actorSystem: ActorSystem = ActorSystem()
import actorSystem.dispatcher
@@ -61,5 +62,4 @@ object MultipartFormUploadPekkoServer extends App {
binding
}
- Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute)
-}
+ Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute).discard
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 7c1334a6b2..06260ce091 100644
--- a/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala
@@ -9,7 +9,7 @@ import zio.http.{Response => ZioHttpResponse, Routes, Server}
import zio.{Task, ZIO, _}
/** Based on https://adopt-tapir.softwaremill.com zio version. */
-object ZioMetricsExample extends ZIOAppDefault {
+object ZioMetricsExample extends ZIOAppDefault:
case class User(name: String) extends AnyVal
@@ -27,7 +27,7 @@ object ZioMetricsExample extends ZIOAppDefault {
val metricsInterceptor: MetricsRequestInterceptor[Task] = metrics.metricsInterceptor()
// noinspection DuplicatedCode
- override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] = {
+ override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] =
val serverOptions: ZioHttpServerOptions[Any] =
ZioHttpServerOptions.customiseInterceptors.metricsInterceptor(metricsInterceptor).options
val app: Routes[Any, ZioHttpResponse] = ZioHttpInterpreter(serverOptions).toHttp(all)
@@ -44,6 +44,3 @@ object ZioMetricsExample extends ZIOAppDefault {
Server.live
)
.exitCode
- }
-
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/observability/DatadogMetricsExample.scala b/examples/src/main/scala/sttp/tapir/examples/observability/datadogMetricsExample.scala
similarity index 93%
rename from examples/src/main/scala/sttp/tapir/examples/observability/DatadogMetricsExample.scala
rename to examples/src/main/scala/sttp/tapir/examples/observability/datadogMetricsExample.scala
index 89fff38076..cf487b2105 100644
--- a/examples/src/main/scala/sttp/tapir/examples/observability/DatadogMetricsExample.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/observability/datadogMetricsExample.scala
@@ -1,7 +1,6 @@
package sttp.tapir.examples.observability
import com.timgroup.statsd.NonBlockingStatsDClientBuilder
-import sttp.tapir.examples.logging.Logging
import io.circe.generic.auto.*
import sttp.tapir.*
import sttp.tapir.generic.auto.*
@@ -15,8 +14,10 @@ import scala.concurrent.duration.*
import scala.concurrent.{Await, Future}
import scala.io.StdIn
-object DatadogMetricsExample extends App with Logging {
+import org.slf4j.{Logger, LoggerFactory}
+val logger: Logger = LoggerFactory.getLogger(getClass.getName)
+@main def datadogMetricsExample(): Unit =
case class Person(name: String)
// Simple endpoint returning 200 or 400 response with string body
@@ -61,4 +62,3 @@ object DatadogMetricsExample extends App with Logging {
} yield stop
Await.result(program, Duration.Inf)
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/observability/OpenTelemetryMetricsExample.scala b/examples/src/main/scala/sttp/tapir/examples/observability/openTelemetryMetricsExample.scala
similarity index 96%
rename from examples/src/main/scala/sttp/tapir/examples/observability/OpenTelemetryMetricsExample.scala
rename to examples/src/main/scala/sttp/tapir/examples/observability/openTelemetryMetricsExample.scala
index aec1dc1a35..a859631b39 100644
--- a/examples/src/main/scala/sttp/tapir/examples/observability/OpenTelemetryMetricsExample.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/observability/openTelemetryMetricsExample.scala
@@ -1,6 +1,5 @@
package sttp.tapir.examples.observability
-import sttp.tapir.examples.logging.Logging
import io.circe.generic.auto.*
import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter
@@ -57,7 +56,9 @@ import scala.io.StdIn
* ...
* }}}
*/
-object OpenTelemetryMetricsExample extends App with Logging {
+@main def openTelemetryMetricsExample(): Unit =
+ import org.slf4j.{Logger, LoggerFactory}
+ val logger: Logger = LoggerFactory.getLogger(getClass().getName)
case class Person(name: String)
@@ -104,4 +105,3 @@ object OpenTelemetryMetricsExample extends App with Logging {
} yield stop
Await.result(program, Duration.Inf)
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/observability/PrometheusMetricsExample.scala b/examples/src/main/scala/sttp/tapir/examples/observability/prometheusMetricsExample.scala
similarity index 92%
rename from examples/src/main/scala/sttp/tapir/examples/observability/PrometheusMetricsExample.scala
rename to examples/src/main/scala/sttp/tapir/examples/observability/prometheusMetricsExample.scala
index e5933a4a39..76b142ecce 100644
--- a/examples/src/main/scala/sttp/tapir/examples/observability/PrometheusMetricsExample.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/observability/prometheusMetricsExample.scala
@@ -1,6 +1,5 @@
package sttp.tapir.examples.observability
-import sttp.tapir.examples.logging.Logging
import io.circe.generic.auto.*
import sttp.tapir.*
import sttp.tapir.generic.auto.*
@@ -14,7 +13,9 @@ import scala.concurrent.duration.*
import scala.concurrent.{Await, Future}
import scala.io.StdIn
-object PrometheusMetricsExample extends App with Logging {
+@main def prometheusMetricsExample(): Unit =
+ import org.slf4j.{Logger, LoggerFactory}
+ val logger: Logger = LoggerFactory.getLogger(getClass().getName)
case class Person(name: String)
@@ -54,4 +55,3 @@ object PrometheusMetricsExample extends App with Logging {
} yield stop
Await.result(program, Duration.Inf)
-}
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 fb035e8cf2..b954caba00 100644
--- a/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationHttp4sServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationHttp4sServer.scala
@@ -15,7 +15,7 @@ import sttp.tapir.swagger.bundle.SwaggerInterpreter
import java.util.concurrent.atomic.AtomicReference
import scala.concurrent.ExecutionContext
-object MultipleEndpointsDocumentationHttp4sServer extends IOApp {
+object MultipleEndpointsDocumentationHttp4sServer extends IOApp:
// endpoint descriptions
case class Author(name: String)
case class Book(title: String, year: Int, author: Author)
@@ -63,7 +63,7 @@ object MultipleEndpointsDocumentationHttp4sServer extends IOApp {
val routes: HttpRoutes[IO] = booksListingRoutes <+> addBookRoutes <+> swaggerUIRoutes
- override def run(args: List[String]): IO[ExitCode] = {
+ override def run(args: List[String]): IO[ExitCode] =
// starting the server
BlazeServerBuilder[IO]
.withExecutionContext(ec)
@@ -78,5 +78,4 @@ object MultipleEndpointsDocumentationHttp4sServer extends IOApp {
}
}
.as(ExitCode.Success)
- }
-}
+
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 d250989951..0addec9329 100644
--- a/examples/src/main/scala/sttp/tapir/examples/openapi/RedocContextPathHttp4sServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/openapi/RedocContextPathHttp4sServer.scala
@@ -12,23 +12,23 @@ import sttp.tapir.server.http4s.Http4sServerInterpreter
import scala.concurrent.ExecutionContext
-object RedocContextPathHttp4sServer extends IOApp {
+object RedocContextPathHttp4sServer extends IOApp:
val contextPath: List[String] = List("api", "v1")
val docPathPrefix: List[String] = "redoc" :: Nil
val helloWorld: PublicEndpoint[String, Unit, String, Any] =
endpoint.get.in("hello").in(query[String]("name")).out(stringBody)
- val routes: HttpRoutes[IO] = {
+ val routes: HttpRoutes[IO] =
val redocEndpoints = RedocInterpreter(redocUIOptions = RedocUIOptions.default.contextPath(contextPath).pathPrefix(docPathPrefix))
.fromEndpoints[IO](List(helloWorld), "The tapir library", "1.0.0")
Http4sServerInterpreter[IO]().toRoutes(helloWorld.serverLogic(name => IO(s"Hello, $name!".asRight[Unit])) :: redocEndpoints)
- }
+ end routes
implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
- override def run(args: List[String]): IO[ExitCode] = {
+ override def run(args: List[String]): IO[ExitCode] =
// starting the server
BlazeServerBuilder[IO]
.withExecutionContext(ec)
@@ -37,5 +37,3 @@ object RedocContextPathHttp4sServer extends IOApp {
.resource
.use { _ => IO.println(s"go to: http://127.0.0.1:8080/${(contextPath ++ docPathPrefix).mkString("/")}") *> IO.never }
.as(ExitCode.Success)
- }
-}
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 29c1dd5444..517bcbb153 100644
--- a/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala
@@ -10,7 +10,7 @@ import zio.http.{Response => ZioHttpResponse, Routes, Server}
import zio.Console.{printLine, readLine}
import zio.{Task, ZIO, ZIOAppDefault, ZLayer}
-object RedocZioHttpServer extends ZIOAppDefault {
+object RedocZioHttpServer extends ZIOAppDefault:
case class Pet(species: String, url: String)
val petEndpoint: ZServerEndpoint[Any, Any] =
@@ -24,7 +24,7 @@ object RedocZioHttpServer extends ZIOAppDefault {
val redocRoutes: Routes[Any, ZioHttpResponse] =
ZioHttpInterpreter().toHttp(RedocInterpreter().fromServerEndpoints[Task](List(petEndpoint), "Our pets", "1.0"))
- val app = (petRoutes ++ redocRoutes)
+ val app: Routes[Any, ZioHttpResponse] = (petRoutes ++ redocRoutes)
override def run = {
printLine("Go to: http://localhost:8080/docs") *>
@@ -40,4 +40,3 @@ object RedocZioHttpServer extends ZIOAppDefault {
readLine *> fiber.interrupt
}
}.exitCode
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/multipleEndpointsDocumentationPekkoServer.scala
similarity index 96%
rename from examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationPekkoServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/openapi/multipleEndpointsDocumentationPekkoServer.scala
index 8e64d222a3..c45cf28c0f 100644
--- a/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationPekkoServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/openapi/multipleEndpointsDocumentationPekkoServer.scala
@@ -3,6 +3,7 @@ package sttp.tapir.examples.openapi
import java.util.concurrent.atomic.AtomicReference
import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.http.scaladsl.Http
+import ox.discard
import io.circe.generic.auto.*
import sttp.tapir.generic.auto.*
import sttp.tapir.*
@@ -13,7 +14,7 @@ import sttp.tapir.swagger.bundle.SwaggerInterpreter
import scala.concurrent.duration.*
import scala.concurrent.{Await, Future}
-object MultipleEndpointsDocumentationPekkoServer extends App {
+@main def multipleEndpointsDocumentationPekkoServer(): Unit =
implicit val actorSystem: ActorSystem = ActorSystem()
import actorSystem.dispatcher
@@ -75,5 +76,4 @@ object MultipleEndpointsDocumentationPekkoServer extends App {
}
// cleanup
- Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute)
-}
+ Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute).discard
diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/OpenapiExtensions.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/openapiExtensions.scala
similarity index 96%
rename from examples/src/main/scala/sttp/tapir/examples/openapi/OpenapiExtensions.scala
rename to examples/src/main/scala/sttp/tapir/examples/openapi/openapiExtensions.scala
index 3e975f80a9..d8e70756d7 100644
--- a/examples/src/main/scala/sttp/tapir/examples/openapi/OpenapiExtensions.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/openapi/openapiExtensions.scala
@@ -10,7 +10,7 @@ import sttp.tapir.docs.apispec.DocsExtensionAttribute.*
import sttp.tapir.generic.auto.*
import sttp.tapir.json.circe.*
-object OpenapiExtensions extends App {
+@main def openapiExtensions(): Unit =
case class Sample(foo: Boolean, bar: String, baz: Int)
@@ -35,4 +35,3 @@ object OpenapiExtensions extends App {
val openapi = OpenAPIDocsInterpreter().toOpenAPI(sampleEndpoint, Info("title", "1.0"), rootExtensions)
println(openapi.toYaml)
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/SwaggerUIOAuth2PekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/swaggerUIOAuth2PekkoServer.scala
similarity index 94%
rename from examples/src/main/scala/sttp/tapir/examples/openapi/SwaggerUIOAuth2PekkoServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/openapi/swaggerUIOAuth2PekkoServer.scala
index 01fcc65c0c..4ec59601b2 100644
--- a/examples/src/main/scala/sttp/tapir/examples/openapi/SwaggerUIOAuth2PekkoServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/openapi/swaggerUIOAuth2PekkoServer.scala
@@ -2,7 +2,8 @@ package sttp.tapir.examples.openapi
import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.http.scaladsl.Http
-import org.apache.pekko.http.scaladsl.server.{Route, RouteConcatenation}
+import org.apache.pekko.http.scaladsl.server.Route
+import org.apache.pekko.http.scaladsl.server.Directives.*
import sttp.tapir.*
import sttp.tapir.server.PartialServerEndpoint
import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter
@@ -28,7 +29,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 SwaggerUIOAuth2PekkoServer extends App with RouteConcatenation {
+@main def swaggerUIOAuth2PekkoServer(): Unit =
implicit val actorSystem: ActorSystem = ActorSystem()
import actorSystem.dispatcher
@@ -71,4 +72,3 @@ object SwaggerUIOAuth2PekkoServer extends App with RouteConcatenation {
val promise = Promise[Unit]()
Await.result(promise.future, 100.minutes)
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/schema/CustomisingSchemas.scala b/examples/src/main/scala/sttp/tapir/examples/schema/customisingSchemas.scala
similarity index 98%
rename from examples/src/main/scala/sttp/tapir/examples/schema/CustomisingSchemas.scala
rename to examples/src/main/scala/sttp/tapir/examples/schema/customisingSchemas.scala
index 9ed981f9df..af95ba150b 100644
--- a/examples/src/main/scala/sttp/tapir/examples/schema/CustomisingSchemas.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/schema/customisingSchemas.scala
@@ -14,7 +14,7 @@ import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}
/** @see https://tapir.softwaremill.com/en/v1.0.5/endpoint/schemas.html#schema-derivation */
-object CustomisingSchemas extends App {
+@main def customisingSchemas(): Unit =
// schema customised using annotations
val dragonAgeValidator = Validator.min(100)
case class Dragon(@description("how people refer to the dragon") name: String, @validateEach(dragonAgeValidator) age: Option[Int])
@@ -59,4 +59,3 @@ object CustomisingSchemas extends App {
scala.io.StdIn.readLine()
Await.result(serverBinding.stop(), Duration.Inf)
-}
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 a3a2482f22..53e6b747cf 100644
--- a/examples/src/main/scala/sttp/tapir/examples/security/OAuth2GithubHttp4sServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/security/OAuth2GithubHttp4sServer.scala
@@ -19,7 +19,7 @@ import java.time.Instant
import scala.collection.immutable.ListMap
import scala.concurrent.ExecutionContext
-object OAuth2GithubHttp4sServer extends IOApp {
+object OAuth2GithubHttp4sServer extends IOApp:
implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
@@ -105,7 +105,7 @@ object OAuth2GithubHttp4sServer extends IOApp {
val httpClient = AsyncHttpClientCatsBackend.resource[IO]()
- override def run(args: List[String]): IO[ExitCode] = {
+ override def run(args: List[String]): IO[ExitCode] =
// starting the server
httpClient
.use(backend =>
@@ -123,5 +123,3 @@ object OAuth2GithubHttp4sServer extends IOApp {
}
)
.as(ExitCode.Success)
- }
-}
diff --git a/examples2/src/main/scala/sttp/tapir/examples2/security/ServerSecurityLogicZio.scala b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicZio.scala
similarity index 95%
rename from examples2/src/main/scala/sttp/tapir/examples2/security/ServerSecurityLogicZio.scala
rename to examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicZio.scala
index 37c41191e2..bd42b8413b 100644
--- a/examples2/src/main/scala/sttp/tapir/examples2/security/ServerSecurityLogicZio.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicZio.scala
@@ -1,4 +1,4 @@
-package sttp.tapir.examples2.security
+package sttp.tapir.examples.security
import sttp.client3._
import sttp.client3.asynchttpclient.zio.AsyncHttpClientZioBackend
@@ -8,7 +8,7 @@ import sttp.tapir.ztapir._
import zio.http.{Response => ZioHttpResponse, Routes, Server}
import zio.{Console, ExitCode, IO, Scope, Task, ZIO, ZIOAppDefault, ZLayer}
-object ServerSecurityLogicZio extends ZIOAppDefault {
+object ServerSecurityLogicZio extends ZIOAppDefault:
// authentication data structure & logic
case class User(name: String)
case class AuthenticationToken(value: String)
@@ -57,7 +57,7 @@ object ServerSecurityLogicZio extends ZIOAppDefault {
// interpreting as an app
val routes: Routes[Any, ZioHttpResponse] = ZioHttpInterpreter().toHttp(secureHelloWorldWithLogic)
- override def run: ZIO[Scope, Throwable, ExitCode] = {
+ override def run: ZIO[Scope, Throwable, ExitCode] =
def testWith(backend: SttpBackend[Task, Any], port: Int, path: String, salutation: String, token: String): Task[String] =
basicRequest
.response(asStringAlways)
@@ -75,9 +75,7 @@ object ServerSecurityLogicZio extends ZIOAppDefault {
_ <- testWith(backend, port, "hello", "Hello", "apple").map(r => assert(r == "1001"))
_ <- testWith(backend, port, "hello", "Hello", "smurf").map(r => assert(r == "Not saying hello to Gargamel!"))
} yield ()).exitCode
- .provideSome(
+ .provideSome[Scope](
ZLayer.succeed(Server.Config.default.port(8080)),
Server.live
)
- }
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/security/basicAuthenticationPekkoServer.scala
similarity index 97%
rename from examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationPekkoServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/security/basicAuthenticationPekkoServer.scala
index f5fb7c9726..dc7843a681 100644
--- a/examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationPekkoServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/security/basicAuthenticationPekkoServer.scala
@@ -14,7 +14,7 @@ import sttp.tapir.server.pekkohttp.*
import scala.concurrent.duration.*
import scala.concurrent.{Await, Future}
-object BasicAuthenticationPekkoServer extends App {
+@main def basicAuthenticationPekkoServer(): Unit =
implicit val actorSystem: ActorSystem = ActorSystem()
import actorSystem.dispatcher
@@ -46,4 +46,3 @@ object BasicAuthenticationPekkoServer extends App {
}
Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute)
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/security/CorsInterceptorPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/security/corsInterceptorPekkoServer.scala
similarity index 97%
rename from examples/src/main/scala/sttp/tapir/examples/security/CorsInterceptorPekkoServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/security/corsInterceptorPekkoServer.scala
index b761bfe3ba..e50d3924b0 100644
--- a/examples/src/main/scala/sttp/tapir/examples/security/CorsInterceptorPekkoServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/security/corsInterceptorPekkoServer.scala
@@ -6,7 +6,6 @@ import org.apache.pekko.http.scaladsl.server.Route
import sttp.client3.*
import sttp.model.{Header, HeaderNames, Method, StatusCode}
-import scala.util.{Failure, Success}
import sttp.model.headers.Origin
import sttp.tapir.*
import sttp.tapir.server.interceptor.cors.{CORSConfig, CORSInterceptor}
@@ -15,7 +14,7 @@ import sttp.tapir.server.pekkohttp.*
import scala.concurrent.duration.*
import scala.concurrent.{Await, Future}
-object CorsInterceptorPekkoServer extends App {
+@main def corsInterceptorPekkoServer(): Unit =
implicit val actorSystem: ActorSystem = ActorSystem()
import actorSystem.dispatcher
@@ -94,4 +93,3 @@ object CorsInterceptorPekkoServer extends App {
}
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
similarity index 98%
rename from examples/src/main/scala/sttp/tapir/examples/security/ExternalSecurityInterceptor.scala
rename to examples/src/main/scala/sttp/tapir/examples/security/externalSecurityInterceptor.scala
index c00dd3cb12..e68eaa7775 100644
--- a/examples/src/main/scala/sttp/tapir/examples/security/ExternalSecurityInterceptor.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/security/externalSecurityInterceptor.scala
@@ -26,7 +26,7 @@ import scala.concurrent.duration.Duration
* (using an attribute), which are allowed to access the endpoint. Verification, if the endpoint can be accessed is done using an
* [[EndpointInterceptor]].
*/
-object ExternalSecurityInterceptor extends App {
+@main def externalSecurityInterceptor(): Unit =
// the sidecar/gateway should add the authenticated role in this header
val roleHeader = "X-Role"
@@ -104,4 +104,3 @@ object ExternalSecurityInterceptor extends App {
assert(secretRequest("2").header("X-Role", "role2").send(backend).code == StatusCode(200))
assert(secretRequest("2").header("X-Role", "role3").send(backend).code == StatusCode(200))
} finally Await.result(serverBinding.stop(), Duration.Inf)
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicPekko.scala b/examples/src/main/scala/sttp/tapir/examples/security/serverSecurityLogicPekko.scala
similarity index 98%
rename from examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicPekko.scala
rename to examples/src/main/scala/sttp/tapir/examples/security/serverSecurityLogicPekko.scala
index d2e3b1185e..956cc89ced 100644
--- a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicPekko.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/security/serverSecurityLogicPekko.scala
@@ -14,7 +14,7 @@ import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter
import scala.concurrent.duration.*
import scala.concurrent.{Await, Future}
-object ServerSecurityLogicPekko extends App {
+@main def serverSecurityLogicPekko(): Unit =
implicit val actorSystem: ActorSystem = ActorSystem()
import actorSystem.dispatcher
@@ -89,4 +89,3 @@ object ServerSecurityLogicPekko extends App {
}
Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 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
similarity index 97%
rename from examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicRefreshCookiesPekko.scala
rename to examples/src/main/scala/sttp/tapir/examples/security/serverSecurityLogicRefreshCookiesPekko.scala
index 22b2369e67..50fa7e67cf 100644
--- a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicRefreshCookiesPekko.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/security/serverSecurityLogicRefreshCookiesPekko.scala
@@ -15,7 +15,7 @@ import sttp.tapir.server.{PartialServerEndpointWithSecurityOutput, ServerEndpoin
import scala.concurrent.duration.*
import scala.concurrent.{Await, Future}
-object ServerSecurityLogicRefreshCookiesPekko extends App {
+@main def serverSecurityLogicRefreshCookiesPekko(): Unit =
implicit val actorSystem: ActorSystem = ActorSystem()
import actorSystem.dispatcher
@@ -70,4 +70,3 @@ object ServerSecurityLogicRefreshCookiesPekko extends App {
}
Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 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
deleted file mode 100644
index d1e80ef4f9..0000000000
--- a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesNettyServer.scala
+++ /dev/null
@@ -1,16 +0,0 @@
-package sttp.tapir.examples.static_content
-
-import sttp.tapir.server.netty.NettyFutureServer
-import sttp.tapir.emptyInput
-import sttp.tapir.files.*
-
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.Future
-
-object StaticContentFromFilesNettyServer extends App {
- NettyFutureServer()
- .port(8080)
- .addEndpoints(staticFilesServerEndpoints[Future](emptyInput)("/var/www"))
- .start()
- .flatMap(_ => Future.never)
-}
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
new file mode 100644
index 0000000000..df286a76bd
--- /dev/null
+++ b/examples/src/main/scala/sttp/tapir/examples/static_content/staticContentFromFilesNettyServer.scala
@@ -0,0 +1,12 @@
+package sttp.tapir.examples.static_content
+
+import sttp.shared.Identity
+import sttp.tapir.emptyInput
+import sttp.tapir.files.*
+import sttp.tapir.server.netty.sync.NettySyncServer
+
+@main def staticContentFromFilesNettyServer(): Unit =
+ NettySyncServer()
+ .port(8080)
+ .addEndpoints(staticFilesServerEndpoints[Identity](emptyInput)("/var/www"))
+ .startAndWait()
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
similarity index 97%
rename from examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesPekkoServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/static_content/staticContentFromFilesPekkoServer.scala
index 594a1aa501..e20fe753bb 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
@@ -14,7 +14,7 @@ import java.nio.file.{Files, Path, StandardOpenOption}
import scala.concurrent.duration.DurationInt
import scala.concurrent.{Await, Future}
-object StaticContentFromFilesPekkoServer extends App {
+@main def staticContentFromFilesPekkoServer(): Unit =
implicit val actorSystem: ActorSystem = ActorSystem()
import actorSystem.dispatcher
@@ -53,4 +53,3 @@ object StaticContentFromFilesPekkoServer extends App {
}
Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 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
similarity index 89%
rename from examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromResourcesPekkoServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/static_content/staticContentFromResourcesPekkoServer.scala
index b8f8c2673f..385868090f 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
@@ -11,13 +11,13 @@ import scala.concurrent.duration.DurationInt
import scala.concurrent.{Await, Future}
import scala.io.StdIn
-object StaticContentFromResourcesPekkoServer extends App {
+@main def staticContentFromResourcesPekkoServer(): Unit =
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)(
- StaticContentFromResourcesPekkoServer.getClass.getClassLoader,
+ this.getClass.getClassLoader,
"webapp",
FilesOptions.default.defaultFile(List("index.html"))
)
@@ -30,4 +30,3 @@ object StaticContentFromResourcesPekkoServer extends App {
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/StaticContentSecurePekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/static_content/staticContentSecurePekkoServer.scala
similarity index 97%
rename from examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentSecurePekkoServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/static_content/staticContentSecurePekkoServer.scala
index d1f3d63520..652f7e7a9b 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
@@ -14,7 +14,7 @@ import java.nio.file.{Files, Path, StandardOpenOption}
import scala.concurrent.duration.DurationInt
import scala.concurrent.{Await, Future}
-object StaticContentSecurePekkoServer extends App {
+@main def staticContentSecurePekkoServer(): Unit =
// creating test files
val exampleDirectory: Path = Files.createTempDirectory("pekko-static-secure-example")
Files.write(exampleDirectory.resolve("f1"), "f1 content".getBytes, StandardOpenOption.CREATE_NEW)
@@ -58,4 +58,3 @@ object StaticContentSecurePekkoServer extends App {
}
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/statusCodeNettyServer.scala
similarity index 59%
rename from examples/src/main/scala/sttp/tapir/examples/status_code/StatusCodeNettyFutureServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/status_code/statusCodeNettyServer.scala
index 457b4332e2..dfa215d2d0 100644
--- a/examples/src/main/scala/sttp/tapir/examples/status_code/StatusCodeNettyFutureServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/status_code/statusCodeNettyServer.scala
@@ -1,22 +1,19 @@
package sttp.tapir.examples.status_code
+import ox.supervised
import sttp.model.{HeaderNames, StatusCode}
import sttp.tapir.*
-import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding}
-
-import scala.concurrent.{Await, Future}
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.duration.Duration
+import sttp.tapir.server.netty.sync.NettySyncServer
/** Three examples of how to return custom status codes */
-object StatusCodeNettyFutureServer extends App {
+@main def statusCodeNettyServer(): Unit =
// An endpoint which always responds with status code 308
val fixedStatusCodeEndpoint = endpoint.get
.in("fixed")
.out(statusCode(StatusCode.PermanentRedirect))
.out(header(HeaderNames.Location, "https://adopt-tapir.softwaremill.com"))
- .serverLogicPure[Future](_ => Right(()))
+ .handleSuccess(_ => ())
//
@@ -26,7 +23,7 @@ object StatusCodeNettyFutureServer extends App {
.errorOut(stringBody)
.out(statusCode)
.out(stringBody)
- .serverLogicPure[Future](code =>
+ .handle(code =>
StatusCode.safeApply(code) match {
// by default, the status code for an error output is 400
case Left(_) => Left(s"Unknown status code: $code")
@@ -53,36 +50,28 @@ object StatusCodeNettyFutureServer extends App {
oneOfDefaultVariant(emptyOutputAs(Unknown)) // by default, the status code is 400
)
)
- .serverLogicPure[Future](kind =>
- kind match {
- case 1 => Left(NotFound("not found")) // status code 404, as defined in oneOfVariant
- case 2 => Left(Unauthorized("secret realm")) // status code 401, as defined in oneOfVariant
- case 3 => Right(()) // status code 200
- case _ => Left(Unknown) // status code 400
- }
- )
+ .handle {
+ case 1 => Left(NotFound("not found")) // status code 404, as defined in oneOfVariant
+ case 2 => Left(Unauthorized("secret realm")) // status code 401, as defined in oneOfVariant
+ case 3 => Right(()) // status code 200
+ case _ => Left(Unknown) // status code 400
+ }
//
// Starting netty server
val declaredPort = 8080
val declaredHost = "localhost"
- val serverBinding: NettyFutureServerBinding =
- Await.result(
- NettyFutureServer()
- .port(declaredPort)
- .host(declaredHost)
- .addEndpoints(List(fixedStatusCodeEndpoint, dynamicStatusCodeEndpoint, oneOfStatusCodeEndpoint))
- .start(),
- Duration.Inf
- )
+ supervised:
+ val binding = NettySyncServer()
+ .port(declaredPort)
+ .host(declaredHost)
+ .addEndpoints(List(fixedStatusCodeEndpoint, dynamicStatusCodeEndpoint, oneOfStatusCodeEndpoint))
+ .start()
- // Bind and start to accept incoming connections.
- val port = serverBinding.port
- val host = serverBinding.hostName
- println(s"Server started at http://$host:$port")
- println("Press any key to stop")
- System.in.read()
+ // Bind and start to accept incoming connections.
+ println(s"Server started at http://${binding.hostName}:${binding.port}")
+ println("Press any key to stop")
+ System.in.read()
- Await.result(serverBinding.stop(), Duration.Inf)
-}
+ binding.stop()
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 91f38362f2..879649db2a 100644
--- a/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala
@@ -1,7 +1,6 @@
package sttp.tapir.examples.streaming
import cats.effect.{ExitCode, IO, IOApp}
-import sttp.tapir.examples.logging.Logging
import fs2.Stream
import org.http4s.HttpRoutes
import org.http4s.blaze.server.BlazeServerBuilder
@@ -14,7 +13,10 @@ import sttp.tapir.*
import sttp.tapir.server.http4s.Http4sServerInterpreter
/** Proxies requests from /proxy to https://httpbin.org/anything */
-object ProxyHttp4sFs2Server extends IOApp with Logging {
+object ProxyHttp4sFs2Server extends IOApp:
+ import org.slf4j.{Logger, LoggerFactory}
+ val logger: Logger = LoggerFactory.getLogger(getClass().getName)
+
val proxyEndpoint: PublicEndpoint[
(Method, List[String], QueryParams, List[Header], Stream[IO, Byte]),
Unit,
@@ -45,7 +47,7 @@ object ProxyHttp4sFs2Server extends IOApp with Logging {
.map { response => (response.headers.toList, response.body) }
})
- override def run(args: List[String]): IO[ExitCode] = {
+ override def run(args: List[String]): IO[ExitCode] =
(for {
backend <- HttpClientFs2Backend.resource[IO]()
routes = proxyRoutes(backend)
@@ -56,5 +58,3 @@ object ProxyHttp4sFs2Server extends IOApp with Logging {
} yield ())
.use { _ => IO.never }
.as(ExitCode.Success)
- }
-}
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 6914631364..18eadf1a8b 100644
--- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala
@@ -17,7 +17,7 @@ import java.nio.charset.StandardCharsets
import scala.concurrent.duration.*
// https://github.com/softwaremill/tapir/issues/367
-object StreamingHttp4sFs2Server extends IOApp {
+object StreamingHttp4sFs2Server extends IOApp:
// corresponds to: GET /receive?name=...
// 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` (set by `streamTextBody`) and the media type is `text/plain`.
@@ -34,7 +34,7 @@ object StreamingHttp4sFs2Server extends IOApp {
Stream
.emit(List[Char]('a', 'b', 'c', 'd'))
.repeat
- .flatMap(list => Stream.chunk(Chunk.seq(list)))
+ .flatMap(list => Stream.chunk(Chunk.from(list)))
.metered[IO](100.millis)
.take(size)
.covary[IO]
@@ -43,7 +43,7 @@ object StreamingHttp4sFs2Server extends IOApp {
.map(s => (size, s))
})
- override def run(args: List[String]): IO[ExitCode] = {
+ override def run(args: List[String]): IO[ExitCode] =
// starting the server
BlazeServerBuilder[IO]
.bindHttp(8080, "localhost")
@@ -51,7 +51,7 @@ object StreamingHttp4sFs2Server extends IOApp {
.resource
.use { _ =>
IO {
- val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend()
+ val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend()
val result: String = basicRequest.response(asStringAlways).get(uri"http://localhost:8080/receive").send(backend).body
println("Got result: " + result)
@@ -59,5 +59,3 @@ object StreamingHttp4sFs2Server extends IOApp {
}
}
.as(ExitCode.Success)
- }
-}
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 41610fda91..c5fada2682 100644
--- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala
@@ -14,7 +14,7 @@ import sttp.tapir.server.netty.cats.{NettyCatsServer, NettyCatsServerBinding}
import java.nio.charset.StandardCharsets
import scala.concurrent.duration.*
-object StreamingNettyFs2Server extends IOApp {
+object StreamingNettyFs2Server extends IOApp:
// corresponds to: GET /receive?name=...
// 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` (set by `streamTextBody`) and the media type is `text/plain`.
@@ -42,12 +42,11 @@ object StreamingNettyFs2Server extends IOApp {
private val declaredPort = 9090
private val declaredHost = "localhost"
- override def run(args: List[String]): IO[ExitCode] = {
+ override def run(args: List[String]): IO[ExitCode] =
// starting the server
NettyCatsServer
.io()
.use { server =>
-
val startServer: IO[NettyCatsServerBinding[IO]] = server
.port(declaredPort)
.host(declaredHost)
@@ -61,7 +60,7 @@ object StreamingNettyFs2Server extends IOApp {
val host = binding.hostName
println(s"Server started at port = ${binding.port}")
- val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend()
+ val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend()
val result: String =
basicRequest.response(asStringAlways).get(uri"http://$declaredHost:$declaredPort/receive").send(backend).body
println("Got result: " + result)
@@ -70,5 +69,3 @@ object StreamingNettyFs2Server extends IOApp {
}
.as(ExitCode.Success)
}
- }
-}
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 9502fa61e5..469563a166 100644
--- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyZioServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyZioServer.scala
@@ -13,7 +13,7 @@ import zio.stream.*
import java.nio.charset.StandardCharsets
-object StreamingNettyZioServer extends ZIOAppDefault {
+object StreamingNettyZioServer extends ZIOAppDefault:
// corresponds to: GET /receive?name=...
// 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` (set by `streamTextBody`) and the media type is `text/plain`.
@@ -38,7 +38,7 @@ object StreamingNettyZioServer extends ZIOAppDefault {
private val declaredPort = 9090
private val declaredHost = "localhost"
- override def run: URIO[Any, ExitCode] = {
+ override def run: URIO[Any, ExitCode] =
(for {
binding <- NettyZioServer()
.port(declaredPort)
@@ -50,7 +50,7 @@ object StreamingNettyZioServer extends ZIOAppDefault {
val host = binding.hostName
println(s"Server started at port = ${binding.port}")
- val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend()
+ val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend()
val result: String =
basicRequest.response(asStringAlways).get(uri"http://$declaredHost:$declaredPort/receive").send(backend).body
println("Got result: " + result)
@@ -59,5 +59,3 @@ object StreamingNettyZioServer extends ZIOAppDefault {
}
_ <- binding.stop()
} yield ()).exitCode
- }
-}
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 40ba9c5cdb..33c2e74676 100644
--- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala
@@ -12,7 +12,7 @@ import zio.stream.*
import java.nio.charset.StandardCharsets
import java.time.Duration
-object StreamingZioHttpServer extends ZIOAppDefault {
+object StreamingZioHttpServer extends ZIOAppDefault:
// corresponds to: GET /receive?name=...
// 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` (set by `streamTextBody`) and the media type is `text/plain`.
@@ -46,4 +46,3 @@ object StreamingZioHttpServer extends ZIOAppDefault {
Server.live
)
.exitCode
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/streamingPekkoServer.scala
similarity index 93%
rename from examples/src/main/scala/sttp/tapir/examples/streaming/StreamingPekkoServer.scala
rename to examples/src/main/scala/sttp/tapir/examples/streaming/streamingPekkoServer.scala
index 49e730b91c..9c462ca4a2 100644
--- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingPekkoServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/streaming/streamingPekkoServer.scala
@@ -14,7 +14,7 @@ import sttp.tapir.*
import scala.concurrent.{Await, Future}
import scala.concurrent.duration.*
-object StreamingPekkoServer extends App {
+@main def streamingPekkoServer(): Unit =
implicit val actorSystem: ActorSystem = ActorSystem()
import actorSystem.dispatcher
@@ -31,7 +31,7 @@ object StreamingPekkoServer extends App {
// starting the server
val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(streamingRoute).map { binding =>
// testing
- val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend()
+ val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend()
val result: String = basicRequest.response(asStringAlways).get(uri"http://localhost:8080/receive").send(backend).body
println("Got result: " + result)
@@ -41,4 +41,3 @@ object StreamingPekkoServer extends App {
}
Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute)
-}
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 b27357cfa9..4d0046f2b1 100644
--- a/examples/src/main/scala/sttp/tapir/examples/testing/PekkoServerStubInterpreterExample.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/testing/PekkoServerStubInterpreterExample.scala
@@ -15,7 +15,7 @@ import sttp.tapir.server.stub.TapirStubInterpreter
import scala.concurrent.{ExecutionContext, Future}
-class PekkoServerStubInterpreterExample extends AsyncFlatSpec with Matchers {
+class PekkoServerStubInterpreterExample extends AsyncFlatSpec with Matchers:
it should "use custom exception handler" in {
val stubBackend: SttpBackend[Future, Any] = TapirStubInterpreter(PekkoUsersApi.options, SttpBackendStub.asynchronousFuture)
@@ -43,9 +43,8 @@ class PekkoServerStubInterpreterExample extends AsyncFlatSpec with Matchers {
// then
response.map(_.body shouldBe Right("hello user123"))
}
-}
-object PekkoUsersApi {
+object PekkoUsersApi:
val greetUser: ServerEndpoint[Any, Future] = endpoint.get
.in("api" / "users" / "greet")
@@ -64,4 +63,3 @@ object PekkoUsersApi {
)
def options(implicit ec: ExecutionContext): CustomiseInterceptors[Future, PekkoHttpServerOptions] =
PekkoHttpServerOptions.customiseInterceptors.exceptionHandler(exceptionHandler)
-}
diff --git a/examples2/src/main/scala/sttp/tapir/examples2/testing/SttpMockServerClientExample.scala b/examples/src/main/scala/sttp/tapir/examples/testing/SttpMockServerClientExample.scala
similarity index 95%
rename from examples2/src/main/scala/sttp/tapir/examples2/testing/SttpMockServerClientExample.scala
rename to examples/src/main/scala/sttp/tapir/examples/testing/SttpMockServerClientExample.scala
index 678b641b27..f7faebb25f 100644
--- a/examples2/src/main/scala/sttp/tapir/examples2/testing/SttpMockServerClientExample.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/testing/SttpMockServerClientExample.scala
@@ -1,4 +1,4 @@
-package sttp.tapir.examples2.testing
+package sttp.tapir.examples.testing
import io.circe.generic.auto._
import org.mockserver.integration.ClientAndServer.startClientAndServer
@@ -15,7 +15,7 @@ import sttp.tapir.server.mockserver._
import scala.util.Success
-class SttpMockServerClientExample extends AnyFlatSpec with Matchers with BeforeAndAfterAll with BeforeAndAfterEach {
+class SttpMockServerClientExample extends AnyFlatSpec with Matchers with BeforeAndAfterAll with BeforeAndAfterEach:
behavior of "SttpMockServerClient"
private val baseUri = uri"http://localhost:1080"
@@ -59,4 +59,3 @@ class SttpMockServerClientExample extends AnyFlatSpec with Matchers with BeforeA
actual shouldEqual Success(Value(Right(sampleOut)))
}
-}
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 f8b2c770fa..0fa1ba49d6 100644
--- a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketHttp4sServer.scala
+++ b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketHttp4sServer.scala
@@ -23,7 +23,7 @@ import sttp.ws.WebSocket
import scala.concurrent.ExecutionContext
import scala.concurrent.duration.*
-object WebSocketHttp4sServer extends IOApp {
+object WebSocketHttp4sServer extends IOApp:
implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
@@ -69,7 +69,7 @@ object WebSocketHttp4sServer extends IOApp {
val apiDocs = AsyncAPIInterpreter().toAsyncAPI(wsEndpoint, "Byte counter", "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")
- override def run(args: List[String]): IO[ExitCode] = {
+ override def run(args: List[String]): IO[ExitCode] =
// Starting the server
BlazeServerBuilder[IO]
.withExecutionContext(ec)
@@ -107,5 +107,3 @@ object WebSocketHttp4sServer extends IOApp {
.map(_ => println("Counting complete, bye!"))
}
.as(ExitCode.Success)
- }
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketPekkoServer.scala
deleted file mode 100644
index 3837fcb3e3..0000000000
--- a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketPekkoServer.scala
+++ /dev/null
@@ -1,76 +0,0 @@
-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 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.pekkohttp.PekkoHttpBackend
-import sttp.tapir.*
-import sttp.tapir.docs.asyncapi.AsyncAPIInterpreter
-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.{Await, Future}
-
-object WebSocketPekkoServer {
- def main(args: Array[String]): Unit = {
- 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], PekkoStreams with WebSockets] =
- endpoint.get.in("ping").out(webSocketBody[String, CodecFormat.TextPlain, Response, CodecFormat.Json](PekkoStreams))
-
- implicit val actorSystem: ActorSystem = ActorSystem()
- import actorSystem.dispatcher
-
- // Implementation of the web socket: a flow which echoes incoming messages
- val wsRoute: Route =
- PekkoHttpServerInterpreter().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 { 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]) =>
- 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)
- .map(_ => binding)
- }
-
- Await.result(bindAndCheck.flatMap(_.terminate(1.minute)).flatMap(_ => actorSystem.terminate()), 1.minute)
- }
-}
diff --git a/examples/src/main/scala/sttp/tapir/examples/websocket/webSocketPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/websocket/webSocketPekkoServer.scala
new file mode 100644
index 0000000000..7363896264
--- /dev/null
+++ b/examples/src/main/scala/sttp/tapir/examples/websocket/webSocketPekkoServer.scala
@@ -0,0 +1,73 @@
+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 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.pekkohttp.PekkoHttpBackend
+import sttp.tapir.*
+import sttp.tapir.docs.asyncapi.AsyncAPIInterpreter
+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.{Await, Future}
+
+@main def webSocketPekkoServer(): Unit =
+ 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], PekkoStreams with WebSockets] =
+ endpoint.get.in("ping").out(webSocketBody[String, CodecFormat.TextPlain, Response, CodecFormat.Json](PekkoStreams))
+
+ implicit val actorSystem: ActorSystem = ActorSystem()
+ import actorSystem.dispatcher
+
+ // Implementation of the web socket: a flow which echoes incoming messages
+ val wsRoute: Route =
+ PekkoHttpServerInterpreter().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 { 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]) =>
+ 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)
+ .map(_ => binding)
+ }
+
+ Await.result(bindAndCheck.flatMap(_.terminate(1.minute)).flatMap(_ => actorSystem.terminate()), 1.minute)
diff --git a/examples2/src/main/resources/logback.xml b/examples2/src/main/resources/logback.xml
deleted file mode 100644
index e6cee15ae7..0000000000
--- a/examples2/src/main/resources/logback.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
- %date [%thread] %-5level %logger{36} - %msg%n
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/examples2/src/main/scala/sttp/tapir/examples2/HelloWorldAkkaServer.scala b/examples2/src/main/scala/sttp/tapir/examples2/HelloWorldAkkaServer.scala
deleted file mode 100644
index be26b6a47e..0000000000
--- a/examples2/src/main/scala/sttp/tapir/examples2/HelloWorldAkkaServer.scala
+++ /dev/null
@@ -1,38 +0,0 @@
-package sttp.tapir.examples2
-
-import akka.actor.ActorSystem
-import akka.http.scaladsl.Http
-import akka.http.scaladsl.server.Route
-import sttp.shared.Identity
-import sttp.tapir._
-import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter
-
-import scala.concurrent.{Await, Future}
-import scala.concurrent.duration._
-import sttp.client3._
-
-object HelloWorldAkkaServer extends App {
- implicit val actorSystem: ActorSystem = ActorSystem()
- import actorSystem.dispatcher
-
- // the endpoint: single fixed path input ("hello"), single query parameter
- // corresponds to: GET /hello?name=...
- val helloWorld: PublicEndpoint[String, Unit, String, Any] =
- endpoint.get.in("hello").in(query[String]("name")).out(stringBody)
-
- // converting an endpoint to a route (providing server-side logic); extension method comes from imported packages
- val helloWorldRoute: Route =
- AkkaHttpServerInterpreter().toRoute(helloWorld.serverLogicSuccess(name => Future.successful(s"Hello, $name!")))
-
- // starting the server
- val bindAndCheck = Http().newServerAt("localhost", 8080).bindFlow(helloWorldRoute).map { _ =>
- // 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!")
- }
-
- Await.result(bindAndCheck.transformWith { r => actorSystem.terminate().transform(_ => r) }, 1.minute)
-}
diff --git a/examples2/src/main/scala/sttp/tapir/examples2/custom_types/SealedTraitWithDiscriminator.scala b/examples2/src/main/scala/sttp/tapir/examples2/custom_types/SealedTraitWithDiscriminator.scala
deleted file mode 100644
index 075452d6af..0000000000
--- a/examples2/src/main/scala/sttp/tapir/examples2/custom_types/SealedTraitWithDiscriminator.scala
+++ /dev/null
@@ -1,53 +0,0 @@
-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._
-import io.circe.generic.extras.{Configuration => CirceConfiguration}
-import sttp.tapir._
-import sttp.tapir.generic.Configuration
-import sttp.tapir.generic.auto._
-import sttp.tapir.json.circe._
-import sttp.tapir.server.ServerEndpoint
-import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding}
-import sttp.tapir.swagger.bundle.SwaggerInterpreter
-
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.duration.Duration
-import scala.concurrent.{Await, Future}
-
-object SealedTraitWithDiscriminator extends App {
- // data structures
- sealed trait Node
- case class Leaf(value: String) extends Node
- case class Branch(name: String, children: Seq[Node]) extends Node
-
- val discriminatorFieldName = "kind"
-
- // these configs must match: one configures tapir's schema, the other circe's encoding/decoding to/from json
- implicit val tapirConfig: Configuration = Configuration.default.withDiscriminator(discriminatorFieldName)
- // the CirceConfiguration class is a renamed import (see above), as the name would clash with tapir's Configuration
- implicit val circeConfig: CirceConfiguration = CirceConfiguration.default.withDiscriminator(discriminatorFieldName)
-
- // endpoint description; the configs are used when deriving the Schema, Encoder and Decoder, which are implicit
- // parameters to jsonBody[Node]
- val nodesListing: PublicEndpoint[Unit, Unit, Node, Any] = endpoint.get
- .in("nodes")
- .out(jsonBody[Node])
-
- val nodesListingServerEndpoint: ServerEndpoint[Any, Future] =
- nodesListing.serverLogicSuccess[Future](_ => Future.successful(Branch("b", List(Leaf("x"), Leaf("y")))))
-
- val docsEndpoints = SwaggerInterpreter().fromServerEndpoints[Future](List(nodesListingServerEndpoint), "Nodes", "1.0.0")
-
- val serverBinding: NettyFutureServerBinding =
- Await.result(
- NettyFutureServer().addEndpoints(nodesListingServerEndpoint :: docsEndpoints).start(),
- Duration.Inf
- )
-
- println("Go to: http://localhost:8080/docs")
- println("Press any key to exit ...")
- scala.io.StdIn.readLine()
-
- Await.result(serverBinding.stop(), Duration.Inf)
-}
diff --git a/examples2/src/main/scala/sttp/tapir/examples2/websocket/WebSocketAkkaClient.scala b/examples2/src/main/scala/sttp/tapir/examples2/websocket/WebSocketAkkaClient.scala
deleted file mode 100644
index 755c6cda12..0000000000
--- a/examples2/src/main/scala/sttp/tapir/examples2/websocket/WebSocketAkkaClient.scala
+++ /dev/null
@@ -1,52 +0,0 @@
-package sttp.tapir.examples2.websocket
-
-import akka.actor.ActorSystem
-import akka.stream.scaladsl.{Flow, Sink, Source}
-import io.circe.generic.auto._
-import sttp.capabilities.WebSockets
-import sttp.capabilities.akka.AkkaStreams
-import sttp.client3._
-import sttp.client3.akkahttp.AkkaHttpBackend
-import sttp.tapir._
-import sttp.tapir.client.sttp.SttpClientInterpreter
-import sttp.tapir.json.circe._
-import sttp.tapir.generic.auto._
-import sttp.tapir.client.sttp.ws.akkahttp._
-
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.duration._
-import scala.concurrent.{Await, Future}
-object WebSocketAkkaClient extends App {
- case class TestMessage(text: String, counter: Int)
-
- val jsonEchoWsEndpoint: PublicEndpoint[Unit, Unit, Flow[TestMessage, TestMessage, Any], AkkaStreams with WebSockets] =
- endpoint.get.out(webSocketBody[TestMessage, CodecFormat.Json, TestMessage, CodecFormat.Json](AkkaStreams))
-
- implicit val actorSystem: ActorSystem = ActorSystem()
- val backend = AkkaHttpBackend.usingActorSystem(actorSystem)
- val result = SttpClientInterpreter()
- .toClientThrowDecodeFailures(jsonEchoWsEndpoint, Some(uri"wss://echo.websocket.org"), backend)
- .apply(())
- .flatMap {
- case Left(msg) => Future(println(s"Cannot establish web socket: $msg"))
- case Right(serverFlow) =>
- val queue = serverFlow
- .runWith(
- Source(List(TestMessage("msg1", 10), TestMessage("msg2", 20), TestMessage("msg3", 30))),
- Sink.queue()
- )
- ._2
-
- for {
- response1 <- queue.pull()
- _ = println(s"Response 1: $response1")
- response2 <- queue.pull()
- _ = println(s"Response 2: $response2")
- response3 <- queue.pull()
- _ = println(s"Response 3: $response3")
- } yield queue.cancel()
- }
- .flatMap(_ => actorSystem.terminate())
-
- Await.result(result, 1.minute)
-}
diff --git a/json/json4s/src/test/scalajvm/sttp/tapir/json/json4s/TapirJson4sTests.scala b/json/json4s/src/test/scala-2/sttp/tapir/json/json4s/TapirJson4sTests.scala
similarity index 94%
rename from json/json4s/src/test/scalajvm/sttp/tapir/json/json4s/TapirJson4sTests.scala
rename to json/json4s/src/test/scala-2/sttp/tapir/json/json4s/TapirJson4sTests.scala
index 6617b59ecf..a0870c2baf 100644
--- a/json/json4s/src/test/scalajvm/sttp/tapir/json/json4s/TapirJson4sTests.scala
+++ b/json/json4s/src/test/scala-2/sttp/tapir/json/json4s/TapirJson4sTests.scala
@@ -14,6 +14,8 @@ case class Customer(name: String, yearOfBirth: Int, lastPurchase: Option[Long])
case class Item(serialNumber: Long, price: Int)
case class Order(items: Seq[Item], customer: Customer)
+// the tests are run only for scala 2 because https://github.com/json4s/json4s/issues/1035 is only fixed in
+// versions for scala 3.4+ (not LTS)
class TapirJson4sTests extends AnyFlatSpecLike with Matchers {
implicit val serialization: Serialization = org.json4s.jackson.Serialization
diff --git a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServerInterpreter.scala b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServerInterpreter.scala
index 0beab5e08d..8e98694f08 100644
--- a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServerInterpreter.scala
+++ b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServerInterpreter.scala
@@ -15,8 +15,7 @@ import sttp.tapir.server.netty.{NettyResponse, NettyServerRequest, Route}
trait NettySyncServerInterpreter:
def nettyServerOptions: NettySyncServerOptions
- /** Requires implicit supervision scope (Ox), because it needs to know in which scope it can start background forks in the Web Sockets
- * processor.
+ /** Requires supervision scope (Ox), because it needs to know in which scope it can start background forks in the Web Sockets processor.
*/
def toRoute(
ses: List[ServerEndpoint[OxStreams & WebSockets, Identity]],