diff --git a/.github/labeler.yml b/.github/labeler.yml index e703eeb11c..560a28fc68 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -6,9 +6,11 @@ labels: - "build.sbt" - "project/Versions.scala" - "project/plugins.sbt" + - "examples\\/src\\/main\\/scala\\/.*" - label: "dependency" authors: ["softwaremill-ci"] files: - "build.sbt" - "project/Versions.scala" - "project/plugins.sbt" + - "examples\\/src\\/main\\/scala\\/.*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c35d8ca65..1e3b48d6ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,11 @@ jobs: sudo apt-get update sudo apt-get install libidn2-dev libcurl3-dev echo "STTP_NATIVE=1" >> $GITHUB_ENV + - name: Install scala-cli + if: matrix.target-platform == 'JVM' && matrix.java == '21' && matrix.scala-version == '3' + uses: VirtusLab/scala-cli-setup@main + with: + jvm: '' # needed because scala-cli-setup otherwise forces the installation of their default JVM (17) - name: Enable Loom-specific modules if: matrix.java == '21' run: echo "ONLY_LOOM=1" >> $GITHUB_ENV @@ -76,6 +81,9 @@ jobs: - name: Compile documentation if: matrix.target-platform == 'JVM' && matrix.java == '21' run: sbt $SBT_JAVA_OPTS -v compileDocumentation + - name: Verify that examples compile using Scala CLI + if: matrix.target-platform == 'JVM' && matrix.java == '21' && matrix.scala-version == '3' + run: sbt $SBT_JAVA_OPTS -v "project examples3" verifyExamplesCompileUsingScalaCli - name: Test if: matrix.target-platform == 'JVM' && matrix.scala-version == '2.12' uses: nick-fields/retry@v2 @@ -208,15 +216,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 2 - # count number of files changed - - name: Count number of files changed - id: count-changed-files - run: | - N=$(git diff --name-only -r HEAD^1 HEAD | wc -w) - echo "changed_files_num=$N" >> $GITHUB_OUTPUT - name: Launch labeler - # skip if more than one file changed - if: steps.count-changed-files.outputs.changed_files_num == 1 uses: srvaroa/labeler@master env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/README.md b/README.md index d5a6c20885..c9fe5fab1e 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ tapir documentation is available at [tapir.softwaremill.com](http://tapir.softwa Add the following dependency: ```sbt -"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.10.13" ``` Then, import: diff --git a/build.sbt b/build.sbt index 07aaa1cb6a..0893e009ba 100644 --- a/build.sbt +++ b/build.sbt @@ -29,6 +29,7 @@ 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") lazy val generateMimeByExtensionDB = taskKey[Unit]("Generate the mime by extension DB") +lazy val verifyExamplesCompileUsingScalaCli = taskKey[Unit]("Verify that each example compiles using Scala CLI") concurrentRestrictions in Global ++= Seq( Tags.limit(Tags.Test, 1), @@ -65,6 +66,9 @@ val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( val files1 = UpdateVersionInDocs(sLog.value, organization.value, version.value) Def.task { (documentation.jvm(documentationScalaVersion) / mdoc).toTask("").value + // Generating the list only after mdoc is done (as it overrides what's in generated_doc) + // For the root project the sourceDirectory points to src, so ../ will point to the root directory of the project + GenerateListOfExamples(sLog.value, sourceDirectory.value.getParentFile) files1 ++ Seq(file("generated-doc/out")) } }.value, @@ -920,8 +924,8 @@ lazy val jsoniterScala: ProjectMatrix = (projectMatrix in file("json/jsoniter")) .settings( name := "tapir-jsoniter-scala", libraryDependencies ++= Seq( - "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.30.4", - "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.30.4" % Test, + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.30.7", + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.30.7" % Test, scalaTest.value % Test ) ) @@ -2046,7 +2050,8 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples")) logback ), publishArtifact := false, - Compile / run / fork := true + Compile / run / fork := true, + verifyExamplesCompileUsingScalaCli := VerifyExamplesCompileUsingScalaCli(sLog.value, sourceDirectory.value) ) .jvmPlatform(scalaVersions = List(examplesScalaVersion)) .dependsOn( diff --git a/doc/_static/css/custom.css b/doc/_static/css/custom.css new file mode 100644 index 0000000000..46ec85acca --- /dev/null +++ b/doc/_static/css/custom.css @@ -0,0 +1,42 @@ +/* general style for all example tags */ +.example-tag { + border-width: 1px; + border-radius: 9999px; + border-style: solid; + padding-left: 0.5rem; + padding-right: 0.5rem; + margin-right: 0.25rem; + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} + +/* different colors for specific tags */ +.example-effects { + color: rgb(193 21 116); + background-color: rgb(253 242 250); + border-color: rgb(252 206 238); +} + +.example-json { + color: rgb(185 56 21); + background-color: rgb(254 246 238); + border-color: rgb(249 219 175); +} + +.example-server { + color: rgb(6 118 71); + background-color: rgb(236 253 243); + border-color: rgb(169 239 197); +} + +.example-docs { + color: rgb(52 64 84); + background-color: rgb(249 250 251); + border-color: rgb(234 236 240); +} + +.example-client { + color: rgb(6 89 134); + background-color: rgb(240 249 255); + border-color: rgb(185 230 254); +} diff --git a/doc/adopters.md b/doc/adopters.md new file mode 100644 index 0000000000..6b86241a9a --- /dev/null +++ b/doc/adopters.md @@ -0,0 +1,43 @@ +# 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. + +Please email us at [tapir@softwaremill.com](mailto:tapir@softwaremill.com) from your company's email with a link to your logo (if we can use it, of course!) or with details who to kindly ask for permission to feature the logo in tapir's documentation. We'll handle the rest. + +Thank you! + +
+ + + + + + diff --git a/doc/conf.py b/doc/conf.py index 5afa049774..d7df20c3ab 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -101,6 +101,12 @@ # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] +# These paths are either relative to html_static_path +# or fully qualified paths (eg. https://...) +html_css_files = [ + 'css/custom.css', +] + # Custom sidebar templates, must be a dictionary that maps document names # to template names. # diff --git a/doc/endpoint/customtypes.md b/doc/endpoint/customtypes.md index 5e4a71ce11..316b678dbd 100644 --- a/doc/endpoint/customtypes.md +++ b/doc/endpoint/customtypes.md @@ -1,4 +1,4 @@ -# Custom types +# Adding support for custom types To support a custom type, you'll need to provide an implicit `Codec` for that type, or the components to create such a codec. diff --git a/doc/endpoint/enumerations.md b/doc/endpoint/enumerations.md index f07f8c6095..574d5a7fa9 100644 --- a/doc/endpoint/enumerations.md +++ b/doc/endpoint/enumerations.md @@ -274,7 +274,7 @@ case class Foo(aOrB: "a" | "b", optA: Option["a"]) derives Schema ### Creating an enum schema by hand -Creating an enumeration [schema](schema.md) by hand is exactly the same as for any other type. The only difference +Creating an enumeration [schema](schemas.md) by hand is exactly the same as for any other type. The only difference is that an enumeration [validator](validation.md) has to be added to the schema. ## Next diff --git a/doc/endpoint/integrations.md b/doc/endpoint/integrations.md index df96382423..96e3db7d54 100644 --- a/doc/endpoint/integrations.md +++ b/doc/endpoint/integrations.md @@ -1,4 +1,4 @@ -# Datatypes integrations +# Third-party datatype libraries integrations ```{note} Note that the codecs defined by the tapir integrations are used only when the specific types (e.g. enumerations) are diff --git a/doc/endpoint/json.md b/doc/endpoint/json.md index 81116b6638..95f5fc6edc 100644 --- a/doc/endpoint/json.md +++ b/doc/endpoint/json.md @@ -53,7 +53,7 @@ To use [Circe](https://github.com/circe/circe), add the following dependency to "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "@VERSION@" ``` -Next, import the package (or extend the `TapirJsonCirce` trait, see [MyTapir](../mytapir.md)): +Next, import the package (or extend the `TapirJsonCirce` trait, see [MyTapir](../other/mytapir.md)): ```scala mdoc:compile-only import sttp.tapir.json.circe.* @@ -125,7 +125,7 @@ To use [µPickle](http://www.lihaoyi.com/upickle/) add the following dependency "com.softwaremill.sttp.tapir" %% "tapir-json-upickle" % "@VERSION@" ``` -Next, import the package (or extend the `TapirJsonuPickle` trait, see [MyTapir](../mytapir.md) and add `TapirJsonuPickle` not `TapirCirceJson`): +Next, import the package (or extend the `TapirJsonuPickle` trait, see [MyTapir](../other/mytapir.md) and add `TapirJsonuPickle` not `TapirCirceJson`): ```scala mdoc:compile-only import sttp.tapir.json.upickle.* @@ -165,7 +165,7 @@ For **Play 2.9** use: "com.softwaremill.sttp.tapir" %% "tapir-json-play29" % "@VERSION@" ``` -Next, import the package (or extend the `TapirJsonPlay` trait, see [MyTapir](../mytapir.md) and add `TapirJsonPlay` not `TapirCirceJson`): +Next, import the package (or extend the `TapirJsonPlay` trait, see [MyTapir](../other/mytapir.md) and add `TapirJsonPlay` not `TapirCirceJson`): ```scala mdoc:compile-only import sttp.tapir.json.play.* @@ -181,7 +181,7 @@ To use [Spray JSON](https://github.com/spray/spray-json) add the following depen "com.softwaremill.sttp.tapir" %% "tapir-json-spray" % "@VERSION@" ``` -Next, import the package (or extend the `TapirJsonSpray` trait, see [MyTapir](../mytapir.md) and add `TapirJsonSpray` not `TapirCirceJson`): +Next, import the package (or extend the `TapirJsonSpray` trait, see [MyTapir](../other/mytapir.md) and add `TapirJsonSpray` not `TapirCirceJson`): ```scala mdoc:compile-only import sttp.tapir.json.spray.* @@ -197,7 +197,7 @@ To use [Tethys JSON](https://github.com/tethys-json/tethys) add the following de "com.softwaremill.sttp.tapir" %% "tapir-json-tethys" % "@VERSION@" ``` -Next, import the package (or extend the `TapirJsonTethys` trait, see [MyTapir](../mytapir.md) and add `TapirJsonTethys` not `TapirCirceJson`): +Next, import the package (or extend the `TapirJsonTethys` trait, see [MyTapir](../other/mytapir.md) and add `TapirJsonTethys` not `TapirCirceJson`): ```scala mdoc:compile-only import sttp.tapir.json.tethysjson.* @@ -213,7 +213,7 @@ To use [Jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala) add the f "com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "@VERSION@" ``` -Next, import the package (or extend the `TapirJsonJsoniter` trait, see [MyTapir](../mytapir.md) and add `TapirJsonJsoniter` not `TapirCirceJson`): +Next, import the package (or extend the `TapirJsonJsoniter` trait, see [MyTapir](../other/mytapir.md) and add `TapirJsonJsoniter` not `TapirCirceJson`): ```scala mdoc:compile-only import sttp.tapir.json.jsoniter.* @@ -237,7 +237,7 @@ And one of the implementations: "org.json4s" %% "json4s-jackson" % "@JSON4S_VERSION@" ``` -Next, import the package (or extend the `TapirJson4s` trait, see [MyTapir](../mytapir.md) and add `TapirJson4s` instead of `TapirCirceJson`): +Next, import the package (or extend the `TapirJson4s` trait, see [MyTapir](../other/mytapir.md) and add `TapirJson4s` instead of `TapirCirceJson`): ```scala mdoc:compile-only import sttp.tapir.json.json4s.* @@ -259,7 +259,7 @@ To use [zio-json](https://github.com/zio/zio-json), add the following dependency ```scala "com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "@VERSION@" ``` -Next, import the package (or extend the `TapirJsonZio` trait, see [MyTapir](../mytapir.md) and add `TapirJsonZio` instead of `TapirCirceJson`): +Next, import the package (or extend the `TapirJsonZio` trait, see [MyTapir](../other/mytapir.md) and add `TapirJsonZio` instead of `TapirCirceJson`): ```scala mdoc:compile-only import sttp.tapir.json.zio.* diff --git a/doc/endpoint/schemas.md b/doc/endpoint/schemas.md index b2d1a68872..38d998a780 100644 --- a/doc/endpoint/schemas.md +++ b/doc/endpoint/schemas.md @@ -119,7 +119,7 @@ the union type, as it's not possible to generate a runtime check for the generic ### Derivation for string-based constant union types e.g. `type AorB = "a" | "b"` -See [enumerations](enumerations.md#scala-3-string-based-constant-union-types-to-enum) on how to use string-based unions of constant types as enums. +See [enumerations](enumerations.html#scala-3-string-based-constant-union-types-to-enum) on how to use string-based unions of constant types as enums. ## Configuring derivation diff --git a/doc/endpoint/security.md b/doc/endpoint/security.md index 0fd010c1ae..1ccecfa50c 100644 --- a/doc/endpoint/security.md +++ b/doc/endpoint/security.md @@ -31,7 +31,7 @@ base64-encoded username/password combination, use: `basic[UsernamePassword]`. as a string, use: `bearer[String]`. * `auth.oauth2.authorizationCode(authorizationUrl, scopes, tokenUrl, refreshUrl): EndpointInput[String]`: creates an OAuth2 authorization using authorization code - sign in using an auth service (for documentation, requires defining also -the `oauth2-redirect.html`, see [Generating OpenAPI documentation](..docs/openapi.md)) +the `oauth2-redirect.html`, see [Generating OpenAPI documentation](../docs/openapi.md)) ## Authentication challenges diff --git a/doc/examples.md b/doc/examples.md index 704d70b0e2..be8151896a 100644 --- a/doc/examples.md +++ b/doc/examples.md @@ -1,35 +1,18 @@ -# Examples +# Examples by category -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. +The Tapir repository contains a number of how-to guides. If you're missing an example for your use-case, please let us +know by [reporting an issue](https://github.com/softwaremill/tapir)! -## Generate a tapir project +Each example is fully self-contained and can be run using [scala-cli](https://scala-cli.virtuslab.org) (you just need +to copy the content of the file, apart from scala-cli, no additional setup is required!). Hopefully this will make +experimenting with Tapir as frictionless as possible! -You can generate a simple tapir-based project using chosen features, build tool and effect system using [adopt-tapir](https://adopt-tapir.softwaremill.com). +Examples are tagged with the stack being used (Direct-style, cats-effect, ZIO, Future), server implementation, generated +documentation format etc. -Alternatively, you can generate a stub of a tapir-based application directly from the command line with `sbt new softwaremill/tapir.g8`. +```{eval-rst} +.. include:: includes/examples_list.md + :parser: markdown +``` -## Third-party examples -* http4s interpreter: [todo-backend](https://github.com/lolgab/snunit-tapir-example) -* quickstart using http4s: [a gitter8 template](https://codeberg.org/wegtam/http4s-tapir.g8). A new project can be created using: `sbt new https://codeberg.org/wegtam/http4s-tapir.g8.git` -* Scala Native application, [using Nginx Unit](https://github.com/lolgab/snunit-tapir-example). - -## Blogs, articles - -* [Migrating from Akka HTTP to tapir](https://softwaremill.com/migrating-from-akka-http-to-tapir/) -* [Benchmarking Tapir: Part 1](https://softwaremill.com/benchmarking-tapir-part-1/) -* [Benchmarking Tapir: Part 2](https://softwaremill.com/benchmarking-tapir-part-2/) -* [Tapir 1.0 released](https://softwaremill.com/tapir-1-0-released/) -* [Security improvements in tapir 0.19](https://softwaremill.com/security-improvements-in-tapir-0-19/) -* [Tapir serverless: a proof of concept](https://blog.softwaremill.com/tapir-serverless-a-proof-of-concept-6b8c9de4d396) -* [Designing tapir's WebSockets support](https://blog.softwaremill.com/designing-tapirs-websockets-support-ff1573166368) -* [Three easy endpoints](https://blog.softwaremill.com/three-easy-endpoints-a6cbd52b0a6e) -* [tAPIr's Endpoint meets ZIO's IO](https://blog.softwaremill.com/tapirs-endpoint-meets-zio-s-io-3278099c5e10) -* [Describe, then interpret: HTTP endpoints using tapir](https://blog.softwaremill.com/describe-then-interpret-http-endpoints-using-tapir-ac139ba565b0) -* [Functional pancakes](https://blog.softwaremill.com/functional-pancakes-cf70023f0dcb) - -## Videos - -* [ScalaLove 2020: Your HTTP endpoints are data, as well!](https://www.youtube.com/watch?v=yuQNgZgSFIc&t=944s) -* [Scalar 2020: A Functional Scala Stack For 2020](https://www.youtube.com/watch?v=DGlkap5kzGU) -* [ScalaWorld 2019: Designing Programmer-Friendly APIs](https://www.youtube.com/watch?v=I3loMuHnYqw) diff --git a/doc/external.md b/doc/external.md new file mode 100644 index 0000000000..5a9a07188f --- /dev/null +++ b/doc/external.md @@ -0,0 +1,33 @@ +# Articles, videos, other examples + +## Generate a tapir project + +You can generate a simple tapir-based project using chosen features, build tool and effect system using [adopt-tapir](https://adopt-tapir.softwaremill.com). + +Alternatively, you can generate a stub of a tapir-based application directly from the command line with `sbt new softwaremill/tapir.g8`. + +## Third-party examples + +* http4s interpreter: [todo-backend](https://github.com/lolgab/snunit-tapir-example) +* quickstart using http4s: [a gitter8 template](https://codeberg.org/wegtam/http4s-tapir.g8). A new project can be created using: `sbt new https://codeberg.org/wegtam/http4s-tapir.g8.git` +* Scala Native application, [using Nginx Unit](https://github.com/lolgab/snunit-tapir-example). + +## Blogs, articles + +* [Migrating from Akka HTTP to tapir](https://softwaremill.com/migrating-from-akka-http-to-tapir/) +* [Benchmarking Tapir: Part 1](https://softwaremill.com/benchmarking-tapir-part-1/) +* [Benchmarking Tapir: Part 2](https://softwaremill.com/benchmarking-tapir-part-2/) +* [Tapir 1.0 released](https://softwaremill.com/tapir-1-0-released/) +* [Security improvements in tapir 0.19](https://softwaremill.com/security-improvements-in-tapir-0-19/) +* [Tapir serverless: a proof of concept](https://blog.softwaremill.com/tapir-serverless-a-proof-of-concept-6b8c9de4d396) +* [Designing tapir's WebSockets support](https://blog.softwaremill.com/designing-tapirs-websockets-support-ff1573166368) +* [Three easy endpoints](https://blog.softwaremill.com/three-easy-endpoints-a6cbd52b0a6e) +* [tAPIr's Endpoint meets ZIO's IO](https://blog.softwaremill.com/tapirs-endpoint-meets-zio-s-io-3278099c5e10) +* [Describe, then interpret: HTTP endpoints using tapir](https://blog.softwaremill.com/describe-then-interpret-http-endpoints-using-tapir-ac139ba565b0) +* [Functional pancakes](https://blog.softwaremill.com/functional-pancakes-cf70023f0dcb) + +## Videos + +* [ScalaLove 2020: Your HTTP endpoints are data, as well!](https://www.youtube.com/watch?v=yuQNgZgSFIc&t=944s) +* [Scalar 2020: A Functional Scala Stack For 2020](https://www.youtube.com/watch?v=DGlkap5kzGU) +* [ScalaWorld 2019: Designing Programmer-Friendly APIs](https://www.youtube.com/watch?v=I3loMuHnYqw) diff --git a/doc/generate.md b/doc/generate.md new file mode 100644 index 0000000000..29302de31a --- /dev/null +++ b/doc/generate.md @@ -0,0 +1,21 @@ +# Generate a Tapir project + +Not sure how to start? + +We recommend the defaults below (Direct-style stack & Netty server); this requires Java 21+. Otherwise, +you might also try the Future stack + Netty, which works on Java 11+. + +If you'd like to include a JSON endpoint, using the [jsoniter](https://github.com/plokhotnyuk/jsoniter-scala) library +might a good choice! + +```{eval-rst} +.. raw:: html + + +``` diff --git a/doc/generator/sbt-openapi-codegen.md b/doc/generator/sbt-openapi-codegen.md index 96365c94aa..78c29630c0 100644 --- a/doc/generator/sbt-openapi-codegen.md +++ b/doc/generator/sbt-openapi-codegen.md @@ -89,8 +89,9 @@ If `openapiUseHeadTagForObjectName = true`, then the `GET /foo` and `GET /bar` `Baz.scala` file, containing a single `object Baz` with those endpoint definitions; the `PUT /foo` endpoint, by dint of having no tags, would be output to the `TapirGeneratedEndpoints` file, along with any schema and parameter definitions. -Files can be generated from multiple openapi schemas if `openapiAdditionalPackages` is configured; for example -```sbt +Files can be generated from multiple openapi schemas if `openapiAdditionalPackages` is configured; for example: + +```scala openapiAdditionalPackages := List( "sttp.tapir.generated.v1" -> baseDirectory.value / "src" / "main" / "resources" / "openapi_v1.yml") ``` diff --git a/doc/index.md b/doc/index.md index a46fe5fe86..9fe3f0bbe4 100644 --- a/doc/index.md +++ b/doc/index.md @@ -7,36 +7,40 @@ ## Intro -With tapir, you can describe HTTP API endpoints as immutable Scala values. Each endpoint can contain a number of -input and output parameters. An endpoint specification can be interpreted as: - -* a server, given the "business logic": a function, which computes output parameters based on input parameters. - Currently supported: - * [Akka HTTP](server/akkahttp.md) `Route`s/`Directive`s - * [Http4s](server/http4s.md) `HttpRoutes[F]` (using cats-effect or [ZIO](server/zio-http4s.md)) - * [Netty](server/netty.md) (using `Future`s, cats-effect or ZIO) - * [Helidon Níma](server/nima.md) (using JVM 21 Virtual Threads and direct style) - * [Finatra](server/finatra.md) `http.Controller` - * [Pekko HTTP](server/pekkohttp.md) `Route`s/`Directive`s - * [Play](server/play.md) `Route` - * [Vert.X](server/vertx.md) `Router => Route` (using `Future`s, cats-effect or ZIO) - * [ZIO Http](server/ziohttp.md) `Http` - * [Armeria](server/armeria.md) `HttpServiceWithRoutes` (using `Future`s, cats-effect or ZIO) - * [JDK HTTP](server/jdkhttp.md) `HttpHandler` (simple, synchronous API only) - * [aws](server/aws.md) through Lambda/SAM/Terraform - * [gRPC](grpc.md) -* a client, which is a function from input parameters to output parameters. - Currently supported: - * [sttp](client/sttp.md) - * [Play](client/play.md) - * [Http4s](client/http4s.md) -* documentation. Currently supported: - * [OpenAPI](docs/openapi.md) - * [AsyncAPI](docs/asyncapi.md) - * [Json Schema](docs/json-schema.md) - -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 a library to describe HTTP APIs, expose them as a server, consume as a client, and automatically document +using open standards. + +Tapir is fast and developer-friendly. The endpoint definition APIs are crafted with readability and discoverability in +mind. Our Netty-based server is one of the best-performing Scala HTTP servers available. + +```scala +endpoint + .get.in("hello").in(query[String]("name")) + .out(stringBody) + .handleSuccess(name => s"Hello, $name!") +``` + +Tapir integrates with all major Scala stacks, so you can use your favorite approach to Functional Programming, while +leveraging all the benefits that Tapir brings! + +Seamless integration with the Scala and HTTP ecosystems is one of Tapir's major strengths: + +* all popular Scala HTTP server implementations are supported. You can define your entire API using Tapir, or expose +Tapir-managed routes alongside "native" ones. This is especially useful when gradually adopting Tapir, or using it for +selected use-cases. +* the Scala ecosystem is rich with libraries leveraging its type-safety and enhancing the developer's toolbox, +that's why Tapir provides integrations with many of such custom type, JSON and observability libraries +* documentation can be generated in the [OpenAPI](docs/openapi.md), [AsyncAPI](docs/asyncapi.md) and [JSON Schema](docs/json-schema.md) formats + +Depending on how you'd prefer to explore Tapir, this documentation has three main sections: + +1. There's a number of [tutorials](tutorials/01_hello_world.md), which provide a gentle introduction to the library +2. Nothing compares to tinkering with working code, that's why we've prepared [runnable examples](examples.md), + covering solutions to many "everyday" problems +3. Finally, the reference documentation describes all of Tapir's aspects in depth - take a look at the menu on + the left, starting with the "Endpoints" section + +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). @@ -49,64 +53,6 @@ Tapir is licensed under Apache2, the source code is [available on GitHub](https: * **abstraction**: re-use common endpoint definitions, as well as individual inputs/outputs * **library, not a framework**: integrates with your stack -## Adopt a tapir - -```{eval-rst} -.. raw:: html - - -``` - -## 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. - -Please email us at [tapir@softwaremill.com](mailto:tapir@softwaremill.com) from your company's email with a link to your logo (if we can use it, of course!) or with details who to kindly ask for permission to feature the logo in tapir's documentation. We'll handle the rest. - -Thank you! - - - - - - - - - ## Code teaser ```scala mdoc:compile-only @@ -179,16 +125,6 @@ sttp is a family of Scala HTTP-related projects, and currently includes: * [sttp shared](https://github.com/softwaremill/sttp-shared): shared web socket, FP abstractions, capabilities and streaming code. * [sttp apispec](https://github.com/softwaremill/sttp-apispec): OpenAPI, AsyncAPI and JSON Schema models. -## Sponsors - -Development and maintenance of sttp tapir is sponsored by [SoftwareMill](https://softwaremill.com), a software development and consulting company. We help clients scale their business through software. Our areas of expertise include backends, distributed systems, blockchain, machine learning and data analytics. - -[![](https://files.softwaremill.com/logo/logo.png "SoftwareMill")](https://softwaremill.com) - -## Commercial Support - -We offer commercial support for sttp and related technologies, as well as development services. [Contact us](https://softwaremill.com/contact/) to learn more about our offer! - ## Table of contents ```{eval-rst} @@ -197,8 +133,9 @@ We offer commercial support for sttp and related technologies, as well as develo :caption: Getting started quickstart - examples - stability + generate + adopters + support scala_2_3_platforms .. toctree:: @@ -211,6 +148,14 @@ We offer commercial support for sttp and related technologies, as well as develo tutorials/04_errors tutorials/05_multiple_inputs_outputs tutorials/06_error_variants + tutorials/07_cats_effect + +.. toctree:: + :maxdepth: 2 + :caption: How-to's + + examples + external .. toctree:: :maxdepth: 2 @@ -239,6 +184,7 @@ We offer commercial support for sttp and related technologies, as well as develo :maxdepth: 2 :caption: Server interpreters + server/overview server/akkahttp server/http4s server/zio-http4s @@ -292,11 +238,12 @@ We offer commercial support for sttp and related technologies, as well as develo :maxdepth: 2 :caption: Other subjects - other_interpreters - mytapir - grpc - troubleshooting - migrating - goals - contributing + other/stability + other/other_interpreters + other/mytapir + other/grpc + other/troubleshooting + other/migrating + other/goals + other/contributing diff --git a/doc/contributing.md b/doc/other/contributing.md similarity index 100% rename from doc/contributing.md rename to doc/other/contributing.md diff --git a/doc/goals.md b/doc/other/goals.md similarity index 100% rename from doc/goals.md rename to doc/other/goals.md diff --git a/doc/grpc.md b/doc/other/grpc.md similarity index 100% rename from doc/grpc.md rename to doc/other/grpc.md diff --git a/doc/migrating.md b/doc/other/migrating.md similarity index 98% rename from doc/migrating.md rename to doc/other/migrating.md index bbd915f12a..b0904b538f 100644 --- a/doc/migrating.md +++ b/doc/other/migrating.md @@ -14,11 +14,11 @@ - If your code sets `badRequestOnPathErrorIfPathShapeMatches = true` to override the default `false`, you can just remove this in tapir 1.5, it is the new default. - Similarly, if your code sets `.badRequestOnDecodeFailure` on endpoint path input, just remove this attribute. - If your code doesn't change this parameter and you update tapir, you should expect shape-matched path decoding failures to always become 400s, without attempting the next endpoint unless explicitly specified. -- If you want to override this behavior and force trying the next endpoint, add `.onDecodeFailureNextEndpoint` to the input where you expect such handling. See [error handling page](server/errors.md) for details. +- If you want to override this behavior and force trying the next endpoint, add `.onDecodeFailureNextEndpoint` to the input where you expect such handling. See [error handling page](../server/errors.md) for details. ## From 1.2 to 1.3 -- Static content endpoints from `sttp.tapir.static._` are deprecated in favor of the new `tapir-files` module. New methods are in `sttp.tapir.files._`: `staticFilesGetServerEndpoint`, `staticFilesHeadServerEndpoint`, `staticFilesServerEndpoints`, `staticResourcesGetServerEndpoint`, `staticResourcesHeadServerEndpoint`, `staticResourcesServerEndpoints`, etc. See the [updated documentation](endpoint/static.md). +- Static content endpoints from `sttp.tapir.static._` are deprecated in favor of the new `tapir-files` module. New methods are in `sttp.tapir.files._`: `staticFilesGetServerEndpoint`, `staticFilesHeadServerEndpoint`, `staticFilesServerEndpoints`, `staticResourcesGetServerEndpoint`, `staticResourcesHeadServerEndpoint`, `staticResourcesServerEndpoints`, etc. See the [updated documentation](../endpoint/static.md). - Respectively, use `sttp.tapir.files.FilesOptions` instead of `sttp.tapir.static.FilesOptions` - the `cats` integration module has been split into `cats` and `cats-effect`, with the latter containing the `CatsMonadError` class, providing a bridge between the sttp-internal `MonadError` and the cats-effect `Sync` typeclass. If you've been using this directly, you might need to update your dependencies. diff --git a/doc/mytapir.md b/doc/other/mytapir.md similarity index 100% rename from doc/mytapir.md rename to doc/other/mytapir.md diff --git a/doc/other_interpreters.md b/doc/other/other_interpreters.md similarity index 100% rename from doc/other_interpreters.md rename to doc/other/other_interpreters.md diff --git a/doc/stability.md b/doc/other/stability.md similarity index 100% rename from doc/stability.md rename to doc/other/stability.md diff --git a/doc/troubleshooting.md b/doc/other/troubleshooting.md similarity index 100% rename from doc/troubleshooting.md rename to doc/other/troubleshooting.md diff --git a/doc/quickstart.md b/doc/quickstart.md index 3fd1414079..93427ea085 100644 --- a/doc/quickstart.md +++ b/doc/quickstart.md @@ -24,12 +24,3 @@ endpoint. and see where auto-complete gets you! -## Scala 2.12 - -Partial unification is now enabled by default from Scala 2.13. However, if you're using Scala 2.12 or older, and don't -have it already, you'll want to to enable partial unification in the compiler (alternatively, you'll need to manually -provide type arguments in some cases). In sbt, this is: - -```scala -scalacOptions += "-Ypartial-unification" -``` diff --git a/doc/scala_2_3_platforms.md b/doc/scala_2_3_platforms.md index 5534455db6..db64bdab16 100644 --- a/doc/scala_2_3_platforms.md +++ b/doc/scala_2_3_platforms.md @@ -1,10 +1,10 @@ -# Scala 2, Scala 3 & platforms +# Scala 2, Scala 3; JVM, JS & Native 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. +Note that not all modules are available for all combinations of the above. This specifically applies to Scala.JS and +Scala Native, where support is limited. 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 @@ -51,3 +51,13 @@ class MyClass { } } ``` + +## Scala 2.12 + +Partial unification is now enabled by default from Scala 2.13. However, if you're using Scala 2.12 or older, and don't +have it already, you'll want to to enable partial unification in the compiler (alternatively, you'll need to manually +provide type arguments in some cases). In sbt, this is: + +```scala +scalacOptions += "-Ypartial-unification" +``` diff --git a/doc/server/aws.md b/doc/server/aws.md index aaeea4a751..4b3444c7d0 100644 --- a/doc/server/aws.md +++ b/doc/server/aws.md @@ -29,7 +29,7 @@ These are corresponding classes for each of the supported runtime: To start using any of the above add the following dependency: -```sbt +```scala "com.softwaremill.sttp.tapir" %% "tapir-aws-lambda" % "@VERSION@" ``` @@ -40,7 +40,7 @@ Tapir leverages ways of doing it provided by AWS, you can choose from: AWS SAM t You can start by adding one of the following dependencies to your project, and then follow examples: -```sbt +```scala "com.softwaremill.sttp.tapir" %% "tapir-aws-sam" % "@VERSION@" "com.softwaremill.sttp.tapir" %% "tapir-aws-terraform" % "@VERSION@" "com.softwaremill.sttp.tapir" %% "tapir-aws-cdk" % "@VERSION@" diff --git a/doc/server/overview.md b/doc/server/overview.md new file mode 100644 index 0000000000..50af6aac36 --- /dev/null +++ b/doc/server/overview.md @@ -0,0 +1,23 @@ +# Overview of server integrations + +Server interpreters require the endpoint descriptions to be combined with "business logic": functions, which compute +an endpoint's output parameters based on input parameters. + +Tapir integrates with a number of HTTP server implementations, through **server interpreters**. We recommend starting +with the Netty-based server. However, if you already have experience with another server, or are using one in your +project already, just continue doing so, and enjoy seamless Tapir integration! + +Currently supported: +* [Netty](netty.md) (using direct-style, `Future`s, cats-effect or ZIO) +* [Http4s](http4s.md) `HttpRoutes[F]` (using cats-effect or [ZIO](zio-http4s.md)) +* [Pekko HTTP](pekkohttp.md) `Route`s/`Directive`s +* [ZIO Http](ziohttp.md) `Http` +* [Play](play.md) `Route` +* [Akka HTTP](akkahttp.md) `Route`s/`Directive`s +* [Helidon Níma](nima.md) (using JVM 21 Virtual Threads and direct style) +* [Finatra](finatra.md) `http.Controller` +* [Vert.X](vertx.md) `Router => Route` (using `Future`s, cats-effect or ZIO) +* [Armeria](armeria.md) `HttpServiceWithRoutes` (using `Future`s, cats-effect or ZIO) +* [JDK HTTP](jdkhttp.md) `HttpHandler` (simple, synchronous API only) +* [aws](aws.md) through Lambda/SAM/Terraform +* [gRPC](../other/grpc.md) diff --git a/doc/server/zio-http4s.md b/doc/server/zio-http4s.md index ceca6dc951..9a69d155a8 100644 --- a/doc/server/zio-http4s.md +++ b/doc/server/zio-http4s.md @@ -17,7 +17,7 @@ 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](../other/mytapir.md)): ```scala mdoc:compile-only import sttp.tapir.ztapir.* diff --git a/doc/server/ziohttp.md b/doc/server/ziohttp.md index 6fce08f20e..ebbb82d2e0 100644 --- a/doc/server/ziohttp.md +++ b/doc/server/ziohttp.md @@ -17,7 +17,7 @@ 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](../other/mytapir.md)): ```scala mdoc:compile-only import sttp.tapir.ztapir.* diff --git a/doc/support.md b/doc/support.md new file mode 100644 index 0000000000..3dbbb1b16c --- /dev/null +++ b/doc/support.md @@ -0,0 +1,11 @@ +# Support & sponsorship + +## Sponsors + +Development and maintenance of sttp tapir is sponsored by [SoftwareMill](https://softwaremill.com), a software development and consulting company. We help clients scale their business through software. We offer services around migrating and maintaining Java and Scala projects (e.g. to Java 21, or across Scala versions), ML/AI discovery workshops, introducing developer platforms (based on Kubernetes and observability technologies), and others. Our areas of expertise include performant backends, distributed systems, machine learning and data analytics, with a focus on Java, Scala, Kafka, TypeScript and Rust. + +[![](https://files.softwaremill.com/logo/logo.png "SoftwareMill")](https://softwaremill.com) + +## Commercial Support + +We offer commercial support for sttp and related technologies, as well as development services. [Contact us](https://softwaremill.com/contact/) to learn more about our offer! diff --git a/doc/tutorials/01_hello_world.md b/doc/tutorials/01_hello_world.md index fbb46c547a..848a4b1c53 100644 --- a/doc/tutorials/01_hello_world.md +++ b/doc/tutorials/01_hello_world.md @@ -97,7 +97,6 @@ Finally, let's add an output to the endpoint. We'll return the response as a str {emphasize-lines="11"} ```scala - //> using dep com.softwaremill.sttp.tapir::tapir-core:@VERSION@ //> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:@VERSION@ diff --git a/doc/tutorials/06_error_variants.md b/doc/tutorials/06_error_variants.md index cbfe762c5c..83308f5aea 100644 --- a/doc/tutorials/06_error_variants.md +++ b/doc/tutorials/06_error_variants.md @@ -1,5 +1,9 @@ # 6. Error variants +```{note} +The tutorial is also available [as a video](https://www.youtube.com/watch?v=w2ZL3WvhBZ8). +``` + Quite often, there's more than one thing that might go wrong. On the other hand, success can also have many facets. In the previous tutorials we've seen that Tapir includes built-in support for differentiating between successful and diff --git a/doc/tutorials/07_cats_effect.md b/doc/tutorials/07_cats_effect.md new file mode 100644 index 0000000000..0991f26a2b --- /dev/null +++ b/doc/tutorials/07_cats_effect.md @@ -0,0 +1,247 @@ +# 7. Integration with cats-effect & http4s + +[cats-effect](https://github.com/typelevel/cats-effect) is one of the most popular functional effect systems for Scala +(probably also one of the top ones when it comes to pure functional programming in general). So far we've used Tapir in +combination with so-called "direct style", where the server logic is expressed using synchronous, blocking code. + +However, one of Tapir's main strengths is that it integrates with virtually every Scala stack out there. This includes, +first and foremost, cats-effect. Let's see how we can combine the two libraries together. + +## Describing an endpoint + +We'll assume that you are familiar with what's described in the previous tutorials, especially [](01_hello_world.md). +The good news is that most of what's described there applies 1-1 to our scenario, when we want to use cats-effect. The +process of describing endpoints is identical, regardless of what programming style, Scala stack of effect library you +use. + +Hence, we'll start with the same basic endpoint description. We'll be editing the `cats-effect.scala` file: + +```scala +//> using dep com.softwaremill.sttp.tapir::tapir-core:@VERSION@ + +import sttp.tapir.* + +@main def helloWorldTapir(): Unit = + val helloWorldEndpoint = endpoint + .get + .in("hello" / "world") + .in(query[String]("name")) + .out(stringBody) +``` + +```{note} +As a side note, while our previous examples required Java 21+, as they leveraged virtual threads under the hood, the +cats-effect version will work with Java 11+. +``` + +## Server-side logic + +The crucial difference when using Tapir+cats-effect, as compared to the "direct" version is in the way the server +logic is provided. The server logic does, most probably, involve side effects. Typically, this might be querying the +database, writing to Kafka, or invoking other endpoints (though in our example, here we'll constrain ourselves to good +old `println`s). Hence, any operations that the server logic performs should be captured using the `IO` monad. + +That is, given an endpoint with inputs of type `I` and error/success outputs of type `E` and `O`, the shape of the +server logic function should be `I => IO[Either[E, O]]`. In other words: given the input parameters `I`, extracted from +the request, the server logic should return a description of a computation, yielding either error `E` or success `O` +outputs, which will then be mapped to the HTTP response. + +To combine an endpoint description with the server logic, we need to use the `.serverLogic` method. While not always +required, as the type parameter is usually inferred correctly, it's nevertheless beneficial to provide the effect type +parameter explicitly, using `.serverLogic[IO]` in our case: + +{emphasize-lines="2, 4, 12-14"} +```scala +//> using dep com.softwaremill.sttp.tapir::tapir-core:@VERSION@ +//> using dep com.softwaremill.sttp.tapir::tapir-cats:@VERSION@ + +import cats.effect.IO +import sttp.tapir.* + +@main def helloWorldTapir(): Unit = + val helloWorldEndpoint = endpoint.get + .in("hello" / "world") + .in(query[String]("name")) + .out(stringBody) + .serverLogic[IO](name => IO + .println(s"Saying hello to: $name") + .flatMap(_ => IO.pure(Right(s"Hello, $name!")))) +``` + +The server-side logic consists of printing a message to the server's logs (`IO.println(...)`), followed by returning +a pure value - successful result. The `s"Hello, $name"` string that will be mapped to the response needs to be first +wrapped with a `Right` (as we want to use the successful outputs), and then lifted to an `IO` computation description +using `IO.pure`. That way, we obtain a value of type `IO[Either[Unit, String]]`, as required by the endpoint +description. + +## http4s integration + +So far we've described the shape of the endpoint, and coupled it with a function implementing the server logic, with +a matching signature. What we still need to do is to expose the endpoint using a server. + +The server must be "cats-effect-aware" - that is, it must need to know how to deal with server-side logic, which is +expressed in terms of `IO` computation descriptions. So far we've been using the `NettySyncServer`, however here it +won't be useful. Attempting to use it with our endpoint description won't compile, as there would be a mismatch on the +type used to represent effects (`Identity` vs `IO`). + +Instead, we need to use a different server. Tapir provides a couple of choices (which might be useful depending on what +you're already using in your project), but the most popular option in case of cats-effect is the +[http4s](https://http4s.org) server. That's what we're going to do as well: through a Tapir-http4s integration, called +a server interpreter. + +We've already introduced interpreters in the tutorial [](02_openapi_docs.md). In this particular case, the http4s +server interpreter will convert our endpoint description+server logic into a `HttpRoutes[IO]` type. This type is +the representation of HTTP routes that is understandable by the http4s API, and which can be used to expose a server +to the outside world. + +The conversion process is an almost-one-liner (if it wasn't for line length limit!): + +{emphasize-lines="2, 5, 7, 18-19"} +```scala +//> using dep com.softwaremill.sttp.tapir::tapir-core:@VERSION@ +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:@VERSION@ + +import cats.effect.IO +import org.http4s.HttpRoutes +import sttp.tapir.* +import sttp.tapir.server.http4s.Http4sServerInterpreter + +@main def helloWorldTapir(): Unit = + val helloWorldEndpoint = endpoint.get + .in("hello" / "world") + .in(query[String]("name")) + .out(stringBody) + .serverLogic[IO](name => IO + .println(s"Saying hello to: $name") + .flatMap(_ => IO.pure(Right(s"Hello, $name!")))) + + val helloWorldRoutes: HttpRoutes[IO] = Http4sServerInterpreter[IO]() + .toRoutes(helloWorldEndpoint) +``` + +## Exposing the server + +As a final step, we need to expose the routes to the outside world. If you've ever used http4s, the following is fairly +standard code to start a server and handle requests until the application is interrupted or killed: + +{emphasize-lines="3, 5, 7, 8, 12, 24-30"} +```scala +//> using dep com.softwaremill.sttp.tapir::tapir-core:@VERSION@ +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:@VERSION@ +//> using dep org.http4s::http4s-blaze-server:0.23.16 + +import cats.effect.{ExitCode, IO, IOApp} +import org.http4s.HttpRoutes +import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.server.Router +import sttp.tapir.* +import sttp.tapir.server.http4s.Http4sServerInterpreter + +object HelloWorldTapir extends IOApp: + val helloWorldEndpoint = endpoint.get + .in("hello" / "world") + .in(query[String]("name")) + .out(stringBody) + .serverLogic[IO](name => IO + .println(s"Saying hello to: $name") + .flatMap(_ => IO.pure(Right(s"Hello, $name!")))) + + val helloWorldRoutes: HttpRoutes[IO] = Http4sServerInterpreter[IO]() + .toRoutes(helloWorldEndpoint) + + override def run(args: List[String]): IO[ExitCode] = + BlazeServerBuilder[IO] + .bindHttp(8080, "localhost") + .withHttpApp(Router("/" -> helloWorldRoutes).orNotFound) + .resource + .use(_ => IO.never) + .as(ExitCode.Success) +``` + +First of all, you might notice that instead of the `@main` method, we are extending the `IOApp` trait. This is needed, +because not only our endpoint's server logic is expressed using `IO`, but the entire process of starting the server +and handling requests is described as an `IO` computation. Hence, we need to start the application in an `IO`-aware way: +the `IOApp` will handle evaluating the `IO` description and actually running the code. + +Secondly, with http4s we need to use a specific server implementation (http4s itself is only an API to define endpoints - +kind of a middle-man between Tapir and low-level networking code). We can choose from `blaze` and `ember` servers, here +we're using the `blaze` one, which is reflected in the additional dependency and the server configuration constructor: +`BlazeServerBuilder`. + +Finally, we've got the `run` method implementation, which attaches our interpreted route to the root context `/` and +exposes the server on `localhost:8080`. + +```{note} +Note that you could also attach other, non-Tapir-managed routes to the same http4s application. Tapir-interpreted +`HttpRoutes[IO]` can co-exist with routes defined in any other way. +``` + +## Adding documentation + +As a final touch, let's expose documentation using the Swagger UI, just as we did before using the Netty server. +The base process is the same: we first need to call the `SwaggerInterpreter` providing the list of endpoints, for which +documentation should be generated. However, this time we'll provide the `IO` type constructor as the type parameter. + +That way, the server logic implementing the behavior of the swagger endpoints (such as reading the .js/.css/.html +resources) will be expressed in terms of `IO`, and we'll be able to convert them later to http4s routes. And that's +the second step that we need to perform: + +{emphasize-lines="3, 7, 13, 27-32, 37"} +```scala +//> using dep com.softwaremill.sttp.tapir::tapir-core:@VERSION@ +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:@VERSION@ +//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:@VERSION@ +//> using dep org.http4s::http4s-blaze-server:0.23.16 + +import cats.effect.{ExitCode, IO, IOApp} +import cats.syntax.all.* +import org.http4s.HttpRoutes +import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.server.Router +import sttp.tapir.* +import sttp.tapir.server.http4s.Http4sServerInterpreter +import sttp.tapir.swagger.bundle.SwaggerInterpreter + +object HelloWorldTapir extends IOApp: + val helloWorldEndpoint = endpoint.get + .in("hello" / "world") + .in(query[String]("name")) + .out(stringBody) + .serverLogic[IO](name => IO + .println(s"Saying hello to: $name") + .flatMap(_ => IO.pure(Right(s"Hello, $name!")))) + + val helloWorldRoutes: HttpRoutes[IO] = Http4sServerInterpreter[IO]() + .toRoutes(helloWorldEndpoint) + + val swaggerEndpoints = SwaggerInterpreter() + .fromServerEndpoints[IO](List(helloWorldEndpoint), "My App", "1.0") + + val swaggerRoutes: HttpRoutes[IO] = Http4sServerInterpreter[IO]().toRoutes(swaggerEndpoints) + + val allRoutes: HttpRoutes[IO] = helloWorldRoutes <+> swaggerRoutes + + override def run(args: List[String]): IO[ExitCode] = + BlazeServerBuilder[IO] + .bindHttp(8080, "localhost") + .withHttpApp(Router("/" -> allRoutes).orNotFound) + .resource + .use(_ => IO.never) + .as(ExitCode.Success) +``` + +Hence, we first generate endpoint descriptions, which correspond to exposing the Swagger UI (containing the generated +OpenAPI yaml for our `/hello/world` endpoint), which use `IO` to express their server logic. Then, we interpret those +endpoints as `HttpRoutes[IO]`, which we can expose using http4's blaze server. + +## Other concepts covered so far + +We can use JSON integration, error outputs, status codes, and any other Tapir features in the same way as we did so +far with the "synchronous" server! The endpoints are described in the same way, the only thing that changes is how the +server logic is provided. + +## Further reading + +* [Netty-cats interpreter](../server/netty.md) +* [Armeria-cats interpreter](../server/armeria.md) +* [Integration with cats datatypes](../endpoint/customtypes.md) diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala index a6b444ccf7..783c7a9d54 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala @@ -1,3 +1,10 @@ +// {cat=Hello, World!; effects=Future; server=http4s}: Exposing an endpoint using the http4s server + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 +//> using dep org.http4s::http4s-blaze-server:0.23.16 + package sttp.tapir.examples import cats.effect.* diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala index 0ba70e423c..3812edd5c7 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala @@ -1,3 +1,9 @@ +// {cat=Hello, World!; effects=cats-effect; server=Netty}: Exposing an endpoint using the Netty server (cats-effect variant) + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-cats:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples import cats.effect.{IO, IOApp} diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala index 53c6bbf1ca..56c122637b 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala @@ -1,3 +1,9 @@ +// {cat=Hello, World!; effects=ZIO; server=ZIO HTTP; json=ZIO JSON}: Exposing an endpoint using the ZIO HTTP server + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-zio:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-zio-http-server:1.10.13 + package sttp.tapir.examples import sttp.tapir.PublicEndpoint diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala index 35af2d0e8f..7001452c51 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala @@ -1,3 +1,14 @@ +// {cat=Hello, World!; effects=ZIO; server=http4s; json=circe; docs=Swagger UI}: Exposing an endpoint, defined with ZIO and depending on services in the environment, using the http4s server + +//> using option -Ykind-projector +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server-zio:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-zio:1.10.13 +//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep dev.zio::zio-interop-cats:23.1.0.2 + package sttp.tapir.examples import cats.syntax.all.* diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala index 4ae638389f..f8f2248bfe 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala @@ -1,3 +1,13 @@ +// {cat=Hello, World!; effects=ZIO; server=http4s; json=circe; docs=Swagger UI}: Exposing an endpoint using the http4s server + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server-zio:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-zio:1.10.13 +//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep dev.zio::zio-interop-cats:23.1.0.2 + package sttp.tapir.examples import cats.syntax.all.* diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala index f3d49a2264..50fd85f19d 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala @@ -1,3 +1,14 @@ +// {cat=Hello, World!; effects=ZIO; server=zio-http; json=circe; docs=Swagger UI}: Exposing an endpoint using the ZIO HTTP server + +//> using option -Ykind-projector +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-zio-http-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-zio:1.10.13 +//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep dev.zio::zio-interop-cats:23.1.0.2 + package sttp.tapir.examples import io.circe.generic.auto.* diff --git a/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala b/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala index 02618e483b..a1004853db 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala @@ -1,3 +1,12 @@ +// {cat=Hello, World!; effects=ZIO; server=http4s}: Extending a base endpoint (which has the security logic provided), with server logic + +//> using option -Ykind-projector +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server-zio:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-zio:1.10.13 +//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep com.softwaremill.sttp.client3::async-http-client-backend-zio:3.9.7 + package sttp.tapir.examples import org.http4s.* diff --git a/examples/src/main/scala/sttp/tapir/examples/booksExample.scala b/examples/src/main/scala/sttp/tapir/examples/booksExample.scala index 77ff388654..e183e77e7f 100644 --- a/examples/src/main/scala/sttp/tapir/examples/booksExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/booksExample.scala @@ -1,10 +1,20 @@ +// {cat=Hello, World!; effects=Future; server=Pekko HTTP; client=sttp3; JSON=circe; docs=Swagger UI}: A demo of Tapir's capabilities + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-sttp-client:1.10.13 +//> using dep org.apache.pekko::pekko-http:1.0.1 +//> using dep org.apache.pekko::pekko-stream:1.0.3 + package sttp.tapir.examples import sttp.tapir.generic.auto.* @main def booksExample(): Unit = import org.slf4j.{Logger, LoggerFactory} - val logger: Logger = LoggerFactory.getLogger(getClass().getName) + val logger: Logger = LoggerFactory.getLogger(this.getClass().getName) type Limit = Option[Int] type AuthToken = String @@ -115,7 +125,7 @@ import sttp.tapir.generic.auto.* // interpreting the endpoint description and converting it to an pekko-http route, providing the logic which // should be run when the endpoint is invoked. List( - addBook.serverLogic((bookAddLogic _).tupled), + addBook.serverLogic(bookAddLogic.tupled), booksListing.serverLogic(bookListingLogic), booksListingByGenre.serverLogic(bookListingByGenreLogic) ) diff --git a/examples/src/main/scala/sttp/tapir/examples/booksPicklerExample.scala b/examples/src/main/scala/sttp/tapir/examples/booksPicklerExample.scala index 055c4375ed..087f0ddf3b 100644 --- a/examples/src/main/scala/sttp/tapir/examples/booksPicklerExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/booksPicklerExample.scala @@ -1,3 +1,13 @@ +// {cat=Hello, World!; effects=Future; server=Netty; client=sttp3; JSON=Pickler; docs=Swagger UI}: A demo of Tapir's capabilities + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-pickler:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-sttp-client:1.10.13 +//> using dep org.apache.pekko::pekko-http:1.0.1 +//> using dep org.apache.pekko::pekko-stream:1.0.3 + package sttp.tapir.examples import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding} @@ -7,7 +17,7 @@ import scala.concurrent.duration.Duration @main def booksPicklerExample(): Unit = import org.slf4j.{Logger, LoggerFactory} - val logger: Logger = LoggerFactory.getLogger(getClass.getName) + val logger: Logger = LoggerFactory.getLogger(this.getClass.getName) type Limit = Option[Int] type AuthToken = String @@ -121,7 +131,7 @@ import scala.concurrent.duration.Duration // interpreting the endpoint description and converting it to an akka-http route, providing the logic which // should be run when the endpoint is invoked. List( - addBook.serverLogic((bookAddLogic _).tupled), + addBook.serverLogic(bookAddLogic.tupled), booksListing.serverLogic(bookListingLogic), booksListingByGenre.serverLogic(bookListingByGenreLogic) ) 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 076dbd3041..3ea8bfc92d 100644 --- a/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala @@ -1,3 +1,12 @@ +// {cat=Client interpreter; effects=cats-effect; JSON=circe}: Interpreting an endpoint as an http4s client + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-client:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep org.http4s::http4s-circe:0.23.27 +//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep org.http4s::http4s-dsl:0.23.27 + package sttp.tapir.examples.client import cats.effect.{ExitCode, IO, IOApp} @@ -6,11 +15,10 @@ import sttp.tapir.* import sttp.tapir.client.http4s.Http4sClientInterpreter import sttp.tapir.generic.auto.* import sttp.tapir.json.circe.* - import org.slf4j.{Logger, LoggerFactory} -val logger: Logger = LoggerFactory.getLogger(getClass.getName) object Http4sClientExample extends IOApp: + val logger: Logger = LoggerFactory.getLogger(this.getClass.getName) case class User(id: Int, name: String) 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 9a244fd469..22489b2bb4 100644 --- a/examples/src/main/scala/sttp/tapir/examples/custom_types/EndpointWithCustomTypes.scala +++ b/examples/src/main/scala/sttp/tapir/examples/custom_types/EndpointWithCustomTypes.scala @@ -1,3 +1,8 @@ +// {cat=Custom types; json=circe}: Supporting custom types, when used in query or path parameters, as well as part of JSON bodies + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 + package sttp.tapir.examples.custom_types import io.circe.generic.auto.* diff --git a/examples/src/main/scala/sttp/tapir/examples/custom_types/booksExampleSemiauto.scala b/examples/src/main/scala/sttp/tapir/examples/custom_types/booksExampleSemiauto.scala index ba442caeec..f0e2cac31c 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,10 +1,20 @@ +// {cat=Custom types; effects=Future; server=Pekko HTTP; client=sttp3; JSON=circe; docs=Swagger UI}: A demo of Tapir's capabilities using semi-auto derivation + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-sttp-client:1.10.13 +//> using dep org.apache.pekko::pekko-http:1.0.1 +//> using dep org.apache.pekko::pekko-stream:1.0.3 + package sttp.tapir.examples.custom_types import sttp.tapir.Schema @main def booksExampleSemiauto(): Unit = import org.slf4j.{Logger, LoggerFactory} - val logger: Logger = LoggerFactory.getLogger(getClass().getName) + val logger: Logger = LoggerFactory.getLogger(this.getClass().getName) type Limit = Option[Int] type AuthToken = String @@ -124,7 +134,7 @@ import sttp.tapir.Schema // interpreting the endpoint description and converting it to an akka-http route, providing the logic which // should be run when the endpoint is invoked. List( - addBook.serverLogic((bookAddLogic _).tupled), + addBook.serverLogic(bookAddLogic.tupled), booksListing.serverLogic(bookListingLogic), booksListingByGenre.serverLogic(bookListingByGenreLogic) ) diff --git a/examples/src/main/scala/sttp/tapir/examples/custom_types/commaSeparatedQueryParameter.scala b/examples/src/main/scala/sttp/tapir/examples/custom_types/commaSeparatedQueryParameter.scala index 1a87bfea52..2690f377e9 100644 --- a/examples/src/main/scala/sttp/tapir/examples/custom_types/commaSeparatedQueryParameter.scala +++ b/examples/src/main/scala/sttp/tapir/examples/custom_types/commaSeparatedQueryParameter.scala @@ -1,3 +1,9 @@ +// {cat=Custom types; effects=Direct; server=Netty; docs=Swagger UI}: Handling comma-separated query parameters + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.13 + package sttp.tapir.examples.custom_types import ox.supervised 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 index 9ac37e6386..894bf6cdc9 100644 --- a/examples/src/main/scala/sttp/tapir/examples/custom_types/sealedTraitWithDiscriminator.scala +++ b/examples/src/main/scala/sttp/tapir/examples/custom_types/sealedTraitWithDiscriminator.scala @@ -1,3 +1,10 @@ +// {cat=Custom types; effects=Direct; server=Netty; JSON=circe; docs=Swagger UI}: Mapping a sealed trait hierarchy to JSON using a discriminator + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.13 + package sttp.tapir.examples.custom_types import io.circe.Codec as CirceCodec diff --git a/examples/src/main/scala/sttp/tapir/examples/ErrorUnionTypesHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/errors/ErrorUnionTypesHttp4sServer.scala similarity index 87% rename from examples/src/main/scala/sttp/tapir/examples/ErrorUnionTypesHttp4sServer.scala rename to examples/src/main/scala/sttp/tapir/examples/errors/ErrorUnionTypesHttp4sServer.scala index 9c5d437a4a..b56845d6a0 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ErrorUnionTypesHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/errors/ErrorUnionTypesHttp4sServer.scala @@ -1,19 +1,26 @@ -package sttp.tapir.examples +// {cat=Error handling; effects=cats-effect; server=http4s; JSON=circe}: Extending a base secured endpoint with error variants, using union types + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + +package sttp.tapir.examples.errors import cats.effect.* -import cats.syntax.all.* import io.circe.generic.auto.* import org.http4s.HttpRoutes -import org.http4s.server.Router import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.server.Router import sttp.client3.* import sttp.model.StatusCode import sttp.shared.Identity import sttp.tapir.* -import sttp.tapir.server.http4s.Http4sServerInterpreter -import sttp.tapir.json.circe.* import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* import sttp.tapir.server.ServerEndpoint +import sttp.tapir.server.http4s.Http4sServerInterpreter import scala.concurrent.ExecutionContext 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 2a97c02f58..e05bff64cd 100644 --- a/examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala @@ -1,3 +1,11 @@ +// {cat=Error handling; effects=cats-effect; server=Netty; JSON=circe}: Error reporting provided by Iron type refinements + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-cats:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-iron:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples.errors import cats.effect.{IO, IOApp} diff --git a/examples/src/main/scala/sttp/tapir/examples/errors/customErrorsOnDecodeFailurePekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/errors/customErrorsOnDecodeFailurePekkoServer.scala index 07f86310b8..ff8fff9da9 100644 --- a/examples/src/main/scala/sttp/tapir/examples/errors/customErrorsOnDecodeFailurePekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/errors/customErrorsOnDecodeFailurePekkoServer.scala @@ -1,9 +1,16 @@ +// {cat=Error handling; effects=Future; server=Pekko HTTP}: Customising errors that are reported on decode failures (e.g. invalid or missing query parameter) + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep org.apache.pekko::pekko-http:1.0.1 +//> using dep org.apache.pekko::pekko-stream:1.0.3 +//> using dep com.softwaremill.sttp.client3::core:3.9.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} @@ -62,4 +69,4 @@ import sttp.tapir.server.interceptor.decodefailure.{DecodeFailureHandler, Defaul binding } - Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute).discard + val _ = Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) diff --git a/examples/src/main/scala/sttp/tapir/examples/errors/errorOutputsPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/errors/errorOutputsPekkoServer.scala index 7a0972c377..19cfc9eb71 100644 --- a/examples/src/main/scala/sttp/tapir/examples/errors/errorOutputsPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/errors/errorOutputsPekkoServer.scala @@ -1,9 +1,17 @@ +// {cat=Error handling; effects=Future; server=Pekko HTTP; json=circe}: Error and successful outputs + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep org.apache.pekko::pekko-http:1.0.1 +//> using dep org.apache.pekko::pekko-stream:1.0.3 +//> using dep com.softwaremill.sttp.client3::core:3.9.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.* @@ -50,4 +58,4 @@ import scala.concurrent.{Await, Future} binding } - Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute).discard + val _ = Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) diff --git a/examples/src/main/scala/sttp/tapir/examples/helloWorldArmeriaServer.scala b/examples/src/main/scala/sttp/tapir/examples/helloWorldArmeriaServer.scala index bfadc347f5..fc915718f3 100644 --- a/examples/src/main/scala/sttp/tapir/examples/helloWorldArmeriaServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/helloWorldArmeriaServer.scala @@ -1,9 +1,14 @@ +// {cat=Hello, World!; effects=Future; server=Armeria}: Exposing an endpoint using the Armeria server + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-armeria-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples import com.linecorp.armeria.server.Server import sttp.capabilities.armeria.ArmeriaStreams import sttp.client3.{HttpURLConnectionBackend, Identity, SttpBackend, UriContext, asStringAlways, basicRequest} -import sttp.shared.Identity import sttp.tapir.server.armeria.{ArmeriaFutureServerInterpreter, TapirService} import sttp.tapir.* diff --git a/examples/src/main/scala/sttp/tapir/examples/helloWorldJdkHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/helloWorldJdkHttpServer.scala index d466dd7b6f..a274f1585d 100644 --- a/examples/src/main/scala/sttp/tapir/examples/helloWorldJdkHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/helloWorldJdkHttpServer.scala @@ -1,3 +1,9 @@ +// {cat=Hello, World!; effects=Direct; server=JDK Http}: Exposing an endpoint using the built-in JDK HTTP server + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-jdkhttp-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples import sttp.client3.{HttpURLConnectionBackend, Response, SttpBackend, UriContext, asStringAlways, basicRequest} diff --git a/examples/src/main/scala/sttp/tapir/examples/helloWorldNettyFutureServer.scala b/examples/src/main/scala/sttp/tapir/examples/helloWorldNettyFutureServer.scala index efd8a53ac2..c1e1515261 100644 --- a/examples/src/main/scala/sttp/tapir/examples/helloWorldNettyFutureServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/helloWorldNettyFutureServer.scala @@ -1,3 +1,9 @@ +// {cat=Hello, World!; effects=Future; server=Netty}: Exposing an endpoint using the Netty server (Future variant) + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples import sttp.client3.{HttpURLConnectionBackend, SttpBackend, UriContext, asStringAlways, basicRequest} diff --git a/examples/src/main/scala/sttp/tapir/examples/helloWorldNettySyncServer.scala b/examples/src/main/scala/sttp/tapir/examples/helloWorldNettySyncServer.scala index 537d176bd4..9be7794798 100644 --- a/examples/src/main/scala/sttp/tapir/examples/helloWorldNettySyncServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/helloWorldNettySyncServer.scala @@ -1,3 +1,9 @@ +// {cat=Hello, World!; effects=Direct; server=Netty}: Exposing an endpoint using the Netty server (Direct-style variant) + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples import ox.* diff --git a/examples/src/main/scala/sttp/tapir/examples/helloWorldPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/helloWorldPekkoServer.scala index b5fe8e1603..e83879b5db 100644 --- a/examples/src/main/scala/sttp/tapir/examples/helloWorldPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/helloWorldPekkoServer.scala @@ -1,3 +1,9 @@ +// {cat=Hello, World!; effects=Future; server=Pekko HTTP}: Exposing an endpoint using the Pekko HTTP server + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples import org.apache.pekko.actor.ActorSystem 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 4d75eeac56..25dd21a962 100644 --- a/examples/src/main/scala/sttp/tapir/examples/logging/ZioLoggingWithCorrelationIdNettyServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/logging/ZioLoggingWithCorrelationIdNettyServer.scala @@ -1,3 +1,9 @@ +// {cat=Logging; effects=ZIO; server=Netty}: Logging using a correlation id + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-zio:1.10.13 +//> using dep com.softwaremill.sttp.client3::zio:3.9.7 + package sttp.tapir.examples.logging import sttp.client3.httpclient.zio.HttpClientZioBackend @@ -30,7 +36,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() diff --git a/examples/src/main/scala/sttp/tapir/examples/multipart/multipartFormUploadPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/multipart/multipartFormUploadPekkoServer.scala index 424157c8d8..547d8f3f1f 100644 --- a/examples/src/main/scala/sttp/tapir/examples/multipart/multipartFormUploadPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/multipart/multipartFormUploadPekkoServer.scala @@ -1,3 +1,11 @@ +// {cat=Multipart; effects=Future; server=Pekko HTTP}: Uploading a multipart form, with text and file parts + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep org.apache.pekko::pekko-http:1.0.1 +//> using dep org.apache.pekko::pekko-stream:1.0.3 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples.multipart import java.io.PrintWriter @@ -5,7 +13,6 @@ 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.* @@ -62,4 +69,4 @@ import scala.concurrent.duration.* binding } - Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute).discard + val _ = Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) 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 06260ce091..edda1af348 100644 --- a/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala @@ -1,3 +1,9 @@ +// {cat=Observability; effects=ZIO; server=ZIO HTTP}: Reporting Prometheus metrics + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-zio-http-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-zio-metrics:1.10.13 + package sttp.tapir.examples.observability import sttp.tapir.* @@ -27,16 +33,14 @@ 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 & ZIOAppArgs & Scope, Any, Any] = val serverOptions: ZioHttpServerOptions[Any] = ZioHttpServerOptions.customiseInterceptors.metricsInterceptor(metricsInterceptor).options val app: Routes[Any, ZioHttpResponse] = ZioHttpInterpreter(serverOptions).toHttp(all) - val port = sys.env.get("http.port").map(_.toInt).getOrElse(8080) - (for { serverPort <- Server.install(app) - _ <- Console.printLine(s"Server started at http://localhost:${serverPort}. Press ENTER key to exit.") + _ <- Console.printLine(s"Server started at http://localhost:$serverPort. Press ENTER key to exit.") _ <- Console.readLine } yield serverPort) .provide( diff --git a/examples/src/main/scala/sttp/tapir/examples/observability/datadogMetricsExample.scala b/examples/src/main/scala/sttp/tapir/examples/observability/datadogMetricsExample.scala index cf487b2105..c9108d3f8e 100644 --- a/examples/src/main/scala/sttp/tapir/examples/observability/datadogMetricsExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/observability/datadogMetricsExample.scala @@ -1,3 +1,11 @@ +// {cat=Observability; effects=Future; server=Netty; json=circe}: Reporting DataDog metrics + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-datadog-metrics:1.10.13 +//> using dep org.slf4j:slf4j-api:2.0.13 + package sttp.tapir.examples.observability import com.timgroup.statsd.NonBlockingStatsDClientBuilder @@ -15,9 +23,10 @@ import scala.concurrent.{Await, Future} import scala.io.StdIn import org.slf4j.{Logger, LoggerFactory} -val logger: Logger = LoggerFactory.getLogger(getClass.getName) @main def datadogMetricsExample(): Unit = + val logger: Logger = LoggerFactory.getLogger(this.getClass.getName) + case class Person(name: String) // Simple endpoint returning 200 or 400 response with string body diff --git a/examples/src/main/scala/sttp/tapir/examples/observability/openTelemetryMetricsExample.scala b/examples/src/main/scala/sttp/tapir/examples/observability/openTelemetryMetricsExample.scala index a859631b39..7fa39885dc 100644 --- a/examples/src/main/scala/sttp/tapir/examples/observability/openTelemetryMetricsExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/observability/openTelemetryMetricsExample.scala @@ -1,3 +1,12 @@ +// {cat=Observability; effects=Future; server=Netty; json=circe}: Reporting OpenTelemetry metrics + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-opentelemetry-metrics:1.10.13 +//> using dep io.opentelemetry:opentelemetry-exporter-otlp:1.40.0 +//> using dep org.slf4j:slf4j-api:2.0.13 + package sttp.tapir.examples.observability import io.circe.generic.auto.* @@ -12,6 +21,7 @@ import sttp.tapir.json.circe.jsonBody import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.metrics.opentelemetry.OpenTelemetryMetrics import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerOptions} +import org.slf4j.{Logger, LoggerFactory} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration.* @@ -57,8 +67,7 @@ import scala.io.StdIn * }}} */ @main def openTelemetryMetricsExample(): Unit = - import org.slf4j.{Logger, LoggerFactory} - val logger: Logger = LoggerFactory.getLogger(getClass().getName) + val logger: Logger = LoggerFactory.getLogger(this.getClass().getName) case class Person(name: String) diff --git a/examples/src/main/scala/sttp/tapir/examples/observability/prometheusMetricsExample.scala b/examples/src/main/scala/sttp/tapir/examples/observability/prometheusMetricsExample.scala index 76b142ecce..3c10916578 100644 --- a/examples/src/main/scala/sttp/tapir/examples/observability/prometheusMetricsExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/observability/prometheusMetricsExample.scala @@ -1,3 +1,11 @@ +// {cat=Observability; effects=Future; server=Netty; json=circe}: Reporting Prometheus metrics + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-prometheus-metrics:1.10.13 +//> using dep org.slf4j:slf4j-api:2.0.13 + package sttp.tapir.examples.observability import io.circe.generic.auto.* @@ -7,6 +15,7 @@ import sttp.tapir.json.circe.* import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.metrics.prometheus.PrometheusMetrics import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerOptions} +import org.slf4j.{Logger, LoggerFactory} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration.* @@ -14,8 +23,7 @@ import scala.concurrent.{Await, Future} import scala.io.StdIn @main def prometheusMetricsExample(): Unit = - import org.slf4j.{Logger, LoggerFactory} - val logger: Logger = LoggerFactory.getLogger(getClass().getName) + val logger: Logger = LoggerFactory.getLogger(this.getClass().getName) case class Person(name: String) 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 b954caba00..af7dc3be23 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationHttp4sServer.scala @@ -1,3 +1,11 @@ +// {cat=OpenAPI documentation; effects=cats-effect; server=http4s; docs=Swagger UI; json=circe}: Documenting multiple endpoints + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.10.13 +//> using dep org.http4s::http4s-blaze-server:0.23.16 + package sttp.tapir.examples.openapi import cats.effect.* @@ -63,7 +71,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,4 +86,3 @@ 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 0addec9329..4bd63888ba 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/RedocContextPathHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/RedocContextPathHttp4sServer.scala @@ -1,3 +1,10 @@ +// {cat=OpenAPI documentation; effects=cats-effect; server=http4s; docs=ReDoc}: Exposing documentation using ReDoc + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-redoc-bundle:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.10.13 +//> using dep org.http4s::http4s-blaze-server:0.23.16 + package sttp.tapir.examples.openapi import cats.effect.* 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 517bcbb153..c9e344b769 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala @@ -1,3 +1,10 @@ +// {cat=OpenAPI documentation; effects=ZIO; server=ZIO HTTP; json=circe; docs=ReDoc}: Exposing documentation using ReDoc + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-redoc-bundle:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-zio-http-server:1.10.13 + package sttp.tapir.examples.openapi import io.circe.generic.auto.* diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/multipleEndpointsDocumentationPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/multipleEndpointsDocumentationPekkoServer.scala index c45cf28c0f..2d285c7331 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/multipleEndpointsDocumentationPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/multipleEndpointsDocumentationPekkoServer.scala @@ -1,9 +1,15 @@ +// {cat=OpenAPI documentation; effects=Future; server=Pekko HTTP; docs=Swagger UI; json=circe}: Documenting multiple endpoints + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 + 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.* @@ -76,4 +82,4 @@ import scala.concurrent.{Await, Future} } // cleanup - Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute).discard + val _ = Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/openapiExtensions.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/openapiExtensions.scala index d8e70756d7..c5e0c460da 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/openapiExtensions.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/openapiExtensions.scala @@ -1,3 +1,10 @@ +// {cat=OpenAPI documentation; json=circe}: Adding OpenAPI documentation extensions + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-openapi-docs:1.10.13 +//> using dep com.softwaremill.sttp.apispec::openapi-circe-yaml:0.10.0 + package sttp.tapir.examples.openapi import io.circe.generic.auto.* diff --git a/examples/src/main/scala/sttp/tapir/examples/openapi/swaggerUIOAuth2PekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/openapi/swaggerUIOAuth2PekkoServer.scala index 4ec59601b2..c3ebed52d9 100644 --- a/examples/src/main/scala/sttp/tapir/examples/openapi/swaggerUIOAuth2PekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/openapi/swaggerUIOAuth2PekkoServer.scala @@ -1,3 +1,9 @@ +// {cat=OpenAPI documentation; effects=Future; server=Pekko HTTP; docs=Swagger UI}: Securing Swagger UI using OAuth 2 + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 + package sttp.tapir.examples.openapi import org.apache.pekko.actor.ActorSystem diff --git a/examples/src/main/scala/sttp/tapir/examples/schema/customisingSchemas.scala b/examples/src/main/scala/sttp/tapir/examples/schema/customisingSchemas.scala index af95ba150b..3f3ca93730 100644 --- a/examples/src/main/scala/sttp/tapir/examples/schema/customisingSchemas.scala +++ b/examples/src/main/scala/sttp/tapir/examples/schema/customisingSchemas.scala @@ -1,3 +1,10 @@ +// {cat=Schemas; effects=Future; server=Netty; json=circe; docs=Swagger UI}: Customising a derived schema, using annotations, and using implicits + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 + package sttp.tapir.examples.schema import io.circe.generic.auto.* 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 53e6b747cf..0870e64b4a 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/OAuth2GithubHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/OAuth2GithubHttp4sServer.scala @@ -1,3 +1,12 @@ +// {cat=Security; effects=cats-effect; server=http4s; json=circe}: Login using OAuth2, authorization code flow + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.client3::async-http-client-backend-cats:3.9.7 +//> using dep org.http4s::http4s-blaze-server:0.23.16 +//> using dep com.github.jwt-scala::jwt-circe:10.0.1 + package sttp.tapir.examples.security import cats.effect.* diff --git a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicZio.scala b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicZio.scala index bd42b8413b..0b4d277970 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicZio.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicZio.scala @@ -1,3 +1,9 @@ +// {cat=Security; effects=ZIO; server=ZIO HTTP}: Separating security and server logic, with a reusable base endpoint, accepting & refreshing credentials via cookies + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-zio-http-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::async-http-client-backend-zio:3.9.7 + package sttp.tapir.examples.security import sttp.client3._ @@ -39,7 +45,7 @@ object ServerSecurityLogicZio extends ZIOAppDefault: .in("hello") .in(query[String]("salutation")) .out(stringBody) - .mapErrorOut(AuthenticationHelloError)(_.wrapped) + .mapErrorOut(AuthenticationHelloError.apply)(_.wrapped) // returning a 400 with the "why" field from the exception .errorOutVariant[HelloError](oneOfVariant(stringBody.mapTo[NoHelloError])) // defining the remaining server logic (which uses the authenticated user) diff --git a/examples/src/main/scala/sttp/tapir/examples/security/basicAuthenticationPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/security/basicAuthenticationPekkoServer.scala index dc7843a681..bed081b213 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/basicAuthenticationPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/basicAuthenticationPekkoServer.scala @@ -1,3 +1,9 @@ +// {cat=Security; effects=Future; server=Pekko HTTP}: HTTP basic authentication + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples.security import org.apache.pekko.actor.ActorSystem @@ -45,4 +51,4 @@ import scala.concurrent.{Await, Future} binding } - Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) + val _ = 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 index e50d3924b0..8a1e19bc0f 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/corsInterceptorPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/corsInterceptorPekkoServer.scala @@ -1,3 +1,9 @@ +// {cat=Security; effects=Future; server=Pekko HTTP}: CORS interceptor + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples.security import org.apache.pekko.actor.ActorSystem diff --git a/examples/src/main/scala/sttp/tapir/examples/security/externalSecurityInterceptor.scala b/examples/src/main/scala/sttp/tapir/examples/security/externalSecurityInterceptor.scala index e68eaa7775..d5e9058d08 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/externalSecurityInterceptor.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/externalSecurityInterceptor.scala @@ -1,3 +1,9 @@ +// {cat=Security; effects=Future; server=Netty}: Interceptor verifying externally added security credentials + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples.security import sttp.client3.* diff --git a/examples/src/main/scala/sttp/tapir/examples/security/serverSecurityLogicPekko.scala b/examples/src/main/scala/sttp/tapir/examples/security/serverSecurityLogicPekko.scala index 956cc89ced..cfbcc01cfa 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/serverSecurityLogicPekko.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/serverSecurityLogicPekko.scala @@ -1,3 +1,9 @@ +// {cat=Security; effects=Future; server=Pekko HTTP}: Separating security and server logic, with a reusable base endpoint + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples.security import org.apache.pekko.actor.ActorSystem @@ -47,7 +53,7 @@ import scala.concurrent.{Await, Future} .in("hello") .in(query[String]("salutation")) .out(stringBody) - .mapErrorOut(AuthenticationHelloError)(_.wrapped) + .mapErrorOut(AuthenticationHelloError.apply)(_.wrapped) // returning a 400 with the "why" field from the exception .errorOutVariant[HelloError](oneOfVariant(stringBody.mapTo[NoHelloError])) // defining the remaining server logic (which uses the authenticated user) @@ -88,4 +94,4 @@ import scala.concurrent.{Await, Future} binding } - Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) + val _ = 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 index 50fa7e67cf..50be32279f 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/serverSecurityLogicRefreshCookiesPekko.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/serverSecurityLogicRefreshCookiesPekko.scala @@ -1,3 +1,9 @@ +// {cat=Security; effects=Future; server=Pekko HTTP}: Separating security and server logic, with a reusable base endpoint, accepting & refreshing credentials via cookies + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples.security import org.apache.pekko.actor.ActorSystem @@ -69,4 +75,4 @@ import scala.concurrent.{Await, Future} binding } - Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) + val _ = 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 index df286a76bd..ef970ba90e 100644 --- a/examples/src/main/scala/sttp/tapir/examples/static_content/staticContentFromFilesNettyServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/static_content/staticContentFromFilesNettyServer.scala @@ -1,3 +1,9 @@ +// {cat=Static content; effects=Direct; server=Netty}: Serving static files from a directory + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-files:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.10.13 + package sttp.tapir.examples.static_content import sttp.shared.Identity diff --git a/examples/src/main/scala/sttp/tapir/examples/static_content/staticContentFromFilesPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/static_content/staticContentFromFilesPekkoServer.scala index e20fe753bb..b511c4ab57 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 @@ -1,3 +1,10 @@ +// {cat=Static content; effects=Future; server=Pekko HTTP}: Serving static files from a directory, with range requests + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-files:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples.static_content import org.apache.pekko.actor.ActorSystem @@ -52,4 +59,4 @@ import scala.concurrent.{Await, Future} binding } - Await.result(bindAndCheck.flatMap(_.terminate(1.minute)), 1.minute) + val _ = 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 index 385868090f..6942d82cfb 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 @@ -1,3 +1,9 @@ +// {cat=Static content; effects=Future; server=Pekko HTTP}: Serving static files from resources + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-files:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 + package sttp.tapir.examples.static_content import org.apache.pekko.actor.ActorSystem diff --git a/examples/src/main/scala/sttp/tapir/examples/static_content/staticContentSecurePekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/static_content/staticContentSecurePekkoServer.scala index 652f7e7a9b..b4ff8c5a9f 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 @@ -1,3 +1,10 @@ +// {cat=Static content; effects=Future; server=Pekko HTTP}: Serving static files secured with a bearer token + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-files:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples.static_content import org.apache.pekko.actor.ActorSystem diff --git a/examples/src/main/scala/sttp/tapir/examples/status_code/statusCodeNettyServer.scala b/examples/src/main/scala/sttp/tapir/examples/status_code/statusCodeNettyServer.scala index dfa215d2d0..fd32c6c34c 100644 --- a/examples/src/main/scala/sttp/tapir/examples/status_code/statusCodeNettyServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/status_code/statusCodeNettyServer.scala @@ -1,3 +1,8 @@ +// {cat=Status code; effects=Direct; server=Netty}: Serving static files from a directory + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.10.13 + package sttp.tapir.examples.status_code import ox.supervised 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 879649db2a..5269b000db 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala @@ -1,3 +1,10 @@ +// {cat=Streaming; effects=cats-effect; server=http4s}: Proxy requests, handling bodies as fs2 streams + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::fs2:3.9.7 +//> using dep org.http4s::http4s-blaze-server:0.23.16 + package sttp.tapir.examples.streaming import cats.effect.{ExitCode, IO, IOApp} @@ -15,7 +22,7 @@ import sttp.tapir.server.http4s.Http4sServerInterpreter /** Proxies requests from /proxy to https://httpbin.org/anything */ object ProxyHttp4sFs2Server extends IOApp: import org.slf4j.{Logger, LoggerFactory} - val logger: Logger = LoggerFactory.getLogger(getClass().getName) + val logger: Logger = LoggerFactory.getLogger(this.getClass().getName) val proxyEndpoint: PublicEndpoint[ (Method, List[String], QueryParams, List[Header], Stream[IO, Byte]), 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 18eadf1a8b..9ce3711077 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala @@ -1,3 +1,10 @@ +// {cat=Streaming; effects=cats-effect; server=http4s}: Stream response as an fs2 stream + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 +//> using dep org.http4s::http4s-blaze-server:0.23.16 + package sttp.tapir.examples.streaming import cats.effect.{ExitCode, IO, IOApp} diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala index c5fada2682..acaa984bf4 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala @@ -1,3 +1,9 @@ +// {cat=Streaming; effects=cats-effect; server=Netty}: Stream response as an fs2 stream + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-cats:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples.streaming import cats.effect.{ExitCode, IO, IOApp} @@ -30,7 +36,7 @@ object StreamingNettyFs2Server 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] @@ -56,8 +62,6 @@ object StreamingNettyFs2Server extends IOApp: startServer .map { binding => - val port = binding.port - val host = binding.hostName println(s"Server started at port = ${binding.port}") val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend() 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 469563a166..159464e33a 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyZioServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyZioServer.scala @@ -1,3 +1,9 @@ +// {cat=Streaming; effects=ZIO; server=Netty}: Stream response as a ZIO stream + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-zio:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples.streaming import sttp.capabilities.zio.ZioStreams @@ -46,8 +52,6 @@ object StreamingNettyZioServer extends ZIOAppDefault: .addEndpoint(serverEndpoint) .start() _ = { - val port = binding.port - val host = binding.hostName println(s"Server started at port = ${binding.port}") val backend: SttpBackend[Identity, Any] = HttpClientSyncBackend() 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 33c2e74676..bc5f16eeca 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala @@ -1,3 +1,9 @@ +// {cat=Streaming; effects=ZIO; server=ZIO HTTP}: Stream response as a ZIO stream + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-zio-http-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples.streaming import sttp.capabilities.zio.ZioStreams diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/streamingPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/streamingPekkoServer.scala index 9c462ca4a2..c001228efb 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/streamingPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/streamingPekkoServer.scala @@ -1,3 +1,9 @@ +// {cat=Streaming; effects=Future; server=Pekko HTTP}: Stream response as a Pekko stream + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 + package sttp.tapir.examples.streaming import org.apache.pekko.actor.ActorSystem 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 4d0046f2b1..68807d2e83 100644 --- a/examples/src/main/scala/sttp/tapir/examples/testing/PekkoServerStubInterpreterExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/testing/PekkoServerStubInterpreterExample.scala @@ -1,3 +1,11 @@ +// {cat=Testing; effects=Future; server=Pekko HTTP}: Test endpoints using the TapirStubInterpreter + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-sttp-stub-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 +//> using dep org.scalatest::scalatest:3.2.19 + package sttp.tapir.examples.testing import org.scalatest.flatspec.AsyncFlatSpec diff --git a/examples/src/main/scala/sttp/tapir/examples/testing/SttpMockServerClientExample.scala b/examples/src/main/scala/sttp/tapir/examples/testing/SttpMockServerClientExample.scala index f7faebb25f..f85cebef52 100644 --- a/examples/src/main/scala/sttp/tapir/examples/testing/SttpMockServerClientExample.scala +++ b/examples/src/main/scala/sttp/tapir/examples/testing/SttpMockServerClientExample.scala @@ -1,3 +1,12 @@ +// {cat=Testing; json=circe}: Test endpoints using the MockServer client + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.tapir::sttp-mock-server:1.10.13 +//> using dep com.softwaremill.sttp.client3::core:3.9.7 +//> using dep org.mock-server:mockserver-netty:5.15.0 +//> using dep org.scalatest::scalatest:3.2.19 + package sttp.tapir.examples.testing import io.circe.generic.auto._ 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 0fa1ba49d6..53df66f5c4 100644 --- a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketHttp4sServer.scala @@ -1,3 +1,13 @@ +// {cat=WebSocket; effects=cats-effect; server=http4s; json=circe; docs=AsyncAPI}: Describe and implement a WebSocket endpoint + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-asyncapi-docs:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.apispec::asyncapi-circe-yaml:0.10.0 +//> using dep com.softwaremill.sttp.client3::async-http-client-backend-fs2:3.9.7 +//> using dep org.http4s::http4s-blaze-server:0.23.16 + package sttp.tapir.examples.websocket import cats.effect.{ExitCode, IO, IOApp} @@ -32,7 +42,7 @@ object WebSocketHttp4sServer extends IOApp: // The web socket endpoint: GET /count. // We need to provide both the type & media type for the requests, and responses. Here, the requests will be // byte arrays, and responses will be returned as json. - val wsEndpoint: PublicEndpoint[Unit, Unit, Pipe[IO, String, CountResponse], Fs2Streams[IO] with WebSockets] = + val wsEndpoint: PublicEndpoint[Unit, Unit, Pipe[IO, String, CountResponse], Fs2Streams[IO] & WebSockets] = endpoint.get.in("count").out(webSocketBody[String, CodecFormat.TextPlain, CountResponse, CodecFormat.Json](Fs2Streams[IO])) // A pipe which counts the number of bytes received each second diff --git a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketNettySyncServer.scala b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketNettySyncServer.scala index 6dd5ca8926..76b5194762 100644 --- a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketNettySyncServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketNettySyncServer.scala @@ -1,3 +1,8 @@ +// {cat=WebSocket; effects=Direct; server=Netty}: Describe and implement a WebSocket endpoint + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.10.13 + package sttp.tapir.examples.websocket import ox.* diff --git a/examples/src/main/scala/sttp/tapir/examples/websocket/webSocketPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/websocket/webSocketPekkoServer.scala index 7363896264..8c1d1d8e70 100644 --- a/examples/src/main/scala/sttp/tapir/examples/websocket/webSocketPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/websocket/webSocketPekkoServer.scala @@ -1,3 +1,12 @@ +// {cat=WebSocket; effects=Future; server=Pekko HTTP}: Describe and implement a WebSocket endpoint + +//> using dep com.softwaremill.sttp.tapir::tapir-core:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-pekko-http-server:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-asyncapi-docs:1.10.13 +//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.10.13 +//> using dep com.softwaremill.sttp.apispec::asyncapi-circe-yaml:0.10.0 +//> using dep com.softwaremill.sttp.client3::pekko-http-backend:3.9.7 + package sttp.tapir.examples.websocket import io.circe.generic.auto.* @@ -27,7 +36,7 @@ import scala.concurrent.{Await, Future} // 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] = + val wsEndpoint: PublicEndpoint[Unit, Unit, Flow[String, Response, Any], PekkoStreams & WebSockets] = endpoint.get.in("ping").out(webSocketBody[String, CodecFormat.TextPlain, Response, CodecFormat.Json](PekkoStreams)) implicit val actorSystem: ActorSystem = ActorSystem() @@ -70,4 +79,4 @@ import scala.concurrent.{Await, Future} .map(_ => binding) } - Await.result(bindAndCheck.flatMap(_.terminate(1.minute)).flatMap(_ => actorSystem.terminate()), 1.minute) + val _ = Await.result(bindAndCheck.flatMap(_.terminate(1.minute)).flatMap(_ => actorSystem.terminate()), 1.minute) diff --git a/generated-doc/out/_static/css/custom.css b/generated-doc/out/_static/css/custom.css new file mode 100644 index 0000000000..46ec85acca --- /dev/null +++ b/generated-doc/out/_static/css/custom.css @@ -0,0 +1,42 @@ +/* general style for all example tags */ +.example-tag { + border-width: 1px; + border-radius: 9999px; + border-style: solid; + padding-left: 0.5rem; + padding-right: 0.5rem; + margin-right: 0.25rem; + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} + +/* different colors for specific tags */ +.example-effects { + color: rgb(193 21 116); + background-color: rgb(253 242 250); + border-color: rgb(252 206 238); +} + +.example-json { + color: rgb(185 56 21); + background-color: rgb(254 246 238); + border-color: rgb(249 219 175); +} + +.example-server { + color: rgb(6 118 71); + background-color: rgb(236 253 243); + border-color: rgb(169 239 197); +} + +.example-docs { + color: rgb(52 64 84); + background-color: rgb(249 250 251); + border-color: rgb(234 236 240); +} + +.example-client { + color: rgb(6 89 134); + background-color: rgb(240 249 255); + border-color: rgb(185 230 254); +} diff --git a/generated-doc/out/adopters.md b/generated-doc/out/adopters.md new file mode 100644 index 0000000000..6b86241a9a --- /dev/null +++ b/generated-doc/out/adopters.md @@ -0,0 +1,43 @@ +# 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. + +Please email us at [tapir@softwaremill.com](mailto:tapir@softwaremill.com) from your company's email with a link to your logo (if we can use it, of course!) or with details who to kindly ask for permission to feature the logo in tapir's documentation. We'll handle the rest. + +Thank you! + + + + + + + + diff --git a/generated-doc/out/client/http4s.md b/generated-doc/out/client/http4s.md index ad1ae61791..b9fc6f6ccc 100644 --- a/generated-doc/out/client/http4s.md +++ b/generated-doc/out/client/http4s.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-http4s-client" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-http4s-client" % "1.10.13" ``` To interpret an endpoint definition as an `org.http4s.Request[F]`, import: diff --git a/generated-doc/out/client/play.md b/generated-doc/out/client/play.md index ba23b48d1c..573dc067b9 100644 --- a/generated-doc/out/client/play.md +++ b/generated-doc/out/client/play.md @@ -6,13 +6,13 @@ See the [Play framework documentation](https://www.playframework.com/documentati For **Play 3.0**, add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-play-client" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-play-client" % "1.10.13" ``` For **Play 2.9**, add ```scala -"com.softwaremill.sttp.tapir" %% "tapir-play29-client" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-play29-client" % "1.10.13" ``` instead. Furthermore, replace all uses of `sttp.capabilities.pekko.PekkoStreams` in the following code snippets with `sttp.capabilities.akka.AkkaStreams`. @@ -51,7 +51,7 @@ After providing the input parameters, the two following are returned: Example: ```scala -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/generated-doc/out/client/sttp.md b/generated-doc/out/client/sttp.md index 42eb9cd895..8ed4330ed8 100644 --- a/generated-doc/out/client/sttp.md +++ b/generated-doc/out/client/sttp.md @@ -3,7 +3,7 @@ Add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % "1.10.13" ``` To make requests using an endpoint definition using the [sttp client](https://github.com/softwaremill/sttp), import: @@ -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")) @@ -101,7 +101,7 @@ In this case add the following dependencies (note the [`%%%`](https://www.scala- instead of the usual `%%`): ```scala -"com.softwaremill.sttp.tapir" %%% "tapir-sttp-client" % "1.10.12" +"com.softwaremill.sttp.tapir" %%% "tapir-sttp-client" % "1.10.13" "io.github.cquiroz" %%% "scala-java-time" % "2.2.0" // implementations of java.time classes for Scala.JS ``` diff --git a/generated-doc/out/conf.py b/generated-doc/out/conf.py index 5afa049774..d7df20c3ab 100644 --- a/generated-doc/out/conf.py +++ b/generated-doc/out/conf.py @@ -101,6 +101,12 @@ # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] +# These paths are either relative to html_static_path +# or fully qualified paths (eg. https://...) +html_css_files = [ + 'css/custom.css', +] + # Custom sidebar templates, must be a dictionary that maps document names # to template names. # diff --git a/generated-doc/out/docs/asyncapi.md b/generated-doc/out/docs/asyncapi.md index 8eacc0d08b..c41c0c9f49 100644 --- a/generated-doc/out/docs/asyncapi.md +++ b/generated-doc/out/docs/asyncapi.md @@ -3,7 +3,7 @@ To use, add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-asyncapi-docs" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-asyncapi-docs" % "1.10.13" "com.softwaremill.sttp.apispec" %% "asyncapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` @@ -15,16 +15,16 @@ object: ```scala 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 -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 -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/generated-doc/out/docs/json-schema.md b/generated-doc/out/docs/json-schema.md index 51165a54cb..ebc77bae9f 100644 --- a/generated-doc/out/docs/json-schema.md +++ b/generated-doc/out/docs/json-schema.md @@ -3,29 +3,29 @@ You can conveniently generate JSON schema from Tapir schema, which can be derived from your Scala types. Use `TapirSchemaToJsonSchema`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-apispec-docs" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-apispec-docs" % "1.10.13" ``` Schema generation can now be performed like in the following example: ```scala 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 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/generated-doc/out/docs/openapi.md b/generated-doc/out/docs/openapi.md index 59e1b38fbd..1ffce71124 100644 --- a/generated-doc/out/docs/openapi.md +++ b/generated-doc/out/docs/openapi.md @@ -13,7 +13,7 @@ these steps can be done separately, giving you complete control over the process To generate OpenAPI documentation and expose it using the Swagger UI in a single step, first add the dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % "1.10.13" ``` Then, you can interpret a list of endpoints using `SwaggerInterpreter`. The result will be a list of file-serving @@ -22,7 +22,7 @@ with the endpoints for which the documentation is generated, will need in turn t interpreter. For example: ```scala -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.swagger.bundle.SwaggerInterpreter import sttp.tapir.server.netty.{NettyFutureServerInterpreter, FutureRoute} @@ -55,7 +55,7 @@ for details. Similarly as above, you'll need the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-redoc-bundle" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-redoc-bundle" % "1.10.13" ``` And the server endpoints can be generated using the `sttp.tapir.redoc.bundle.RedocInterpreter` class. @@ -65,7 +65,7 @@ And the server endpoints can be generated using the `sttp.tapir.redoc.bundle.Red To generate the docs in the OpenAPI yaml format, add the following dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.10.13" "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` @@ -77,7 +77,7 @@ object: ```scala import sttp.apispec.openapi.OpenAPI -import sttp.tapir._ +import sttp.tapir.* import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter val booksListing = endpoint.in(path[String]("bookId")) @@ -111,7 +111,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 -import sttp.apispec.openapi.circe.yaml._ +import sttp.apispec.openapi.circe.yaml.* println(docs.toYaml) ``` @@ -120,8 +120,8 @@ Or to JSON: ```scala 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)) ``` @@ -133,15 +133,15 @@ For example, generating the OpenAPI 3.0.3 YAML string can be achieved by perform Firstly add dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.10.13" "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "..." // see https://github.com/softwaremill/sttp-apispec ``` and generate the documentation by importing valid extension methods and explicitly specifying the "3.0.3" version in the OpenAPI model: ```scala 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]) @@ -163,19 +163,19 @@ The modules `tapir-swagger-ui` and `tapir-redoc` contain server endpoint definit yaml format, will expose it using the given context path. To use, add as a dependency either `tapir-swagger-ui`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui" % "1.10.13" ``` or `tapir-redoc`: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-redoc" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-redoc" % "1.10.13" ``` Then, you'll need to pass the server endpoints to your server interpreter. For example, using akka-http: ```scala -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 @@ -223,7 +223,7 @@ that is they will form a single security requirement, with multiple schemes, e.g ```scala import sttp.model.headers.WWWAuthenticateChallenge -import sttp.tapir._ +import sttp.tapir.* val multiAuthEndpoint = endpoint.post @@ -247,7 +247,7 @@ can be done in the security logic, server logic, or by mapping the inputs using ```scala 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]) @@ -281,16 +281,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 -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) @@ -329,7 +329,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 -import sttp.tapir._ +import sttp.tapir.* val acceptHeader: EndpointInput[String] = header[String]("Accept").schema(_.hidden(true)) ``` diff --git a/generated-doc/out/endpoint/basics.md b/generated-doc/out/endpoint/basics.md index d1e9038f30..45e7c9252c 100644 --- a/generated-doc/out/endpoint/basics.md +++ b/generated-doc/out/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 -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 -import sttp.tapir._ +import sttp.tapir.* type PublicEndpoint[I, E, O, -R] = Endpoint[Unit, I, E, O, R] ``` @@ -35,7 +35,7 @@ completion returns a `User`, would have the type: ```scala -import sttp.tapir._ +import sttp.tapir.* val userEndpoint: PublicEndpoint[(UUID, Int), String, User, Any] = ??? ``` diff --git a/generated-doc/out/endpoint/contenttype.md b/generated-doc/out/endpoint/contenttype.md index d1c120f109..541d1dea8f 100644 --- a/generated-doc/out/endpoint/contenttype.md +++ b/generated-doc/out/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/generated-doc/out/endpoint/customtypes.md b/generated-doc/out/endpoint/customtypes.md index 1449a1b01d..2e91ae14c5 100644 --- a/generated-doc/out/endpoint/customtypes.md +++ b/generated-doc/out/endpoint/customtypes.md @@ -1,4 +1,4 @@ -# Custom types +# Adding support for custom types To support a custom type, you'll need to provide an implicit `Codec` for that type, or the components to create such a codec. @@ -46,29 +46,26 @@ need to provide two mappings: For example, to support a custom id type: ```scala -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 -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 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/generated-doc/out/endpoint/enumerations.md b/generated-doc/out/endpoint/enumerations.md index 47e6f2700e..20f1a438f3 100644 --- a/generated-doc/out/endpoint/enumerations.md +++ b/generated-doc/out/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 -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 -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 -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") ``` @@ -173,25 +169,24 @@ represent the enumeration's values in the documentation). For example, to use an the circe library for JSON parsing/serialisation, and automatic schema derivation for case classes: ```scala -import io.circe._ -import io.circe.generic.auto._ -import sttp.tapir._ -import sttp.tapir.json.circe._ -import sttp.tapir.generic.auto._ +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 -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 -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 @@ -282,7 +274,7 @@ case class Foo(aOrB: "a" | "b", optA: Option["a"]) derives Schema ### Creating an enum schema by hand -Creating an enumeration [schema](schema.md) by hand is exactly the same as for any other type. The only difference +Creating an enumeration [schema](schemas.md) by hand is exactly the same as for any other type. The only difference is that an enumeration [validator](validation.md) has to be added to the schema. ## Next diff --git a/generated-doc/out/endpoint/forms.md b/generated-doc/out/endpoint/forms.md index b42b3d56ee..14ebe60cce 100644 --- a/generated-doc/out/endpoint/forms.md +++ b/generated-doc/out/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 -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 -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 -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 -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 -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/generated-doc/out/endpoint/integrations.md b/generated-doc/out/endpoint/integrations.md index 5ad0bd039d..67341723f6 100644 --- a/generated-doc/out/endpoint/integrations.md +++ b/generated-doc/out/endpoint/integrations.md @@ -1,4 +1,4 @@ -# Datatypes integrations +# Third-party datatype libraries integrations ```{note} Note that the codecs defined by the tapir integrations are used only when the specific types (e.g. enumerations) are @@ -12,17 +12,17 @@ The `tapir-cats` module contains additional instances for some [cats](https://ty datatypes as well as additional syntax: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-cats" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-cats" % "1.10.13" ``` -- `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: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-cats-effect" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-cats-effect" % "1.10.13" ``` ## Refined integration @@ -31,11 +31,11 @@ If you use [refined](https://github.com/fthomas/refined), the `tapir-refined` mo validators for `T Refined P` as long as a codec for `T` already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-refined" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-refined" % "1.10.13" ``` 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. @@ -52,13 +52,13 @@ If you use [iron](https://github.com/Iltotore/iron), the `tapir-iron` module wil validators for `T :| P` as long as a codec for `T` already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-iron" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-iron" % "1.10.13" ``` 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: @@ -144,10 +145,10 @@ The `tapir-enumeratum` module provides schemas, validators and codecs for [Enume enumerations. To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % "1.10.13" ``` -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`. @@ -157,10 +158,10 @@ If you use [scala-newtype](https://github.com/estatico/scala-newtype), the `tapi schemas for types with a `@newtype` and `@newsubtype` annotations as long as a codec and schema for its underlying value already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-newtype" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-newtype" % "1.10.13" ``` -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 @@ -168,10 +169,10 @@ If you use [monix newtypes](https://github.com/monix/newtypes), the `tapir-monix schemas for types which extend `NewtypeWrapped` and `NewsubtypeWrapped` annotations as long as a codec and schema for its underlying value already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-monix-newtype" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-monix-newtype" % "1.10.13" ``` -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 @@ -179,7 +180,7 @@ If you use [ZIO Prelude Newtypes](https://zio.github.io/zio-prelude/docs/newtype schemas for types defined using `Newtype` and `Subtype` as long as a codec and a schema for the underlying type already exists: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-zio-prelude" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-zio-prelude" % "1.10.13" ``` Then, mix in `sttp.tapir.codec.zio.prelude.newtype.TapirNewtypeSupport` into your newtype to bring the implicit values into scope: @@ -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]] ``` @@ -218,7 +219,7 @@ For details refer to [derevo documentation](https://github.com/tofu-tf/derevo#in To use, add the following dependency: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-derevo" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-derevo" % "1.10.13" ``` Then you can derive schema for your ADT along with other typeclasses besides ADT declaration itself: @@ -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/generated-doc/out/endpoint/ios.md b/generated-doc/out/endpoint/ios.md index efd223d8a1..d55e55f1f3 100644 --- a/generated-doc/out/endpoint/ios.md +++ b/generated-doc/out/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 -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 -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 -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 -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 -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 -import sttp.tapir.EndpointIO.annotations._ +import sttp.tapir.EndpointIO.annotations.* @endpointInput("books/{year}/{genre}") case class Book( diff --git a/generated-doc/out/endpoint/json.md b/generated-doc/out/endpoint/json.md index 0186fb4e37..8391609fbe 100644 --- a/generated-doc/out/endpoint/json.md +++ b/generated-doc/out/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 -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]) ``` @@ -50,13 +50,13 @@ stringJsonBody.schema(implicitly[Schema[MyBody]].as[String]) To use [Circe](https://github.com/circe/circe), add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.10.13" ``` -Next, import the package (or extend the `TapirJsonCirce` trait, see [MyTapir](../mytapir.md)): +Next, import the package (or extend the `TapirJsonCirce` trait, see [MyTapir](../other/mytapir.md)): ```scala -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 -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 -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 -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 @@ -123,28 +122,27 @@ Now the above JSON object will render as To use [µPickle](http://www.lihaoyi.com/upickle/) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-upickle" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-json-upickle" % "1.10.13" ``` -Next, import the package (or extend the `TapirJsonuPickle` trait, see [MyTapir](../mytapir.md) and add `TapirJsonuPickle` not `TapirCirceJson`): +Next, import the package (or extend the `TapirJsonuPickle` trait, see [MyTapir](../other/mytapir.md) and add `TapirJsonuPickle` not `TapirCirceJson`): ```scala -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 -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] ``` @@ -158,19 +156,19 @@ For more examples, including making a custom encoder/decoder, see [TapirJsonuPic To use [Play JSON](https://github.com/playframework/play-json) for **Play 3.0**, add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-play" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-json-play" % "1.10.13" ``` For **Play 2.9** use: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-play29" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-json-play29" % "1.10.13" ``` -Next, import the package (or extend the `TapirJsonPlay` trait, see [MyTapir](../mytapir.md) and add `TapirJsonPlay` not `TapirCirceJson`): +Next, import the package (or extend the `TapirJsonPlay` trait, see [MyTapir](../other/mytapir.md) and add `TapirJsonPlay` not `TapirCirceJson`): ```scala -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. @@ -180,13 +178,13 @@ Play JSON requires `Reads` and `Writes` implicit values in scope for each type y To use [Spray JSON](https://github.com/spray/spray-json) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-spray" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-json-spray" % "1.10.13" ``` -Next, import the package (or extend the `TapirJsonSpray` trait, see [MyTapir](../mytapir.md) and add `TapirJsonSpray` not `TapirCirceJson`): +Next, import the package (or extend the `TapirJsonSpray` trait, see [MyTapir](../other/mytapir.md) and add `TapirJsonSpray` not `TapirCirceJson`): ```scala -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. @@ -196,13 +194,13 @@ Spray JSON requires a `JsonFormat` implicit value in scope for each type you wan To use [Tethys JSON](https://github.com/tethys-json/tethys) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-tethys" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-json-tethys" % "1.10.13" ``` -Next, import the package (or extend the `TapirJsonTethys` trait, see [MyTapir](../mytapir.md) and add `TapirJsonTethys` not `TapirCirceJson`): +Next, import the package (or extend the `TapirJsonTethys` trait, see [MyTapir](../other/mytapir.md) and add `TapirJsonTethys` not `TapirCirceJson`): ```scala -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. @@ -212,13 +210,13 @@ Tethys JSON requires `JsonReader` and `JsonWriter` implicit values in scope for To use [Jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala) add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "1.10.13" ``` -Next, import the package (or extend the `TapirJsonJsoniter` trait, see [MyTapir](../mytapir.md) and add `TapirJsonJsoniter` not `TapirCirceJson`): +Next, import the package (or extend the `TapirJsonJsoniter` trait, see [MyTapir](../other/mytapir.md) and add `TapirJsonJsoniter` not `TapirCirceJson`): ```scala -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. @@ -228,7 +226,7 @@ Jsoniter Scala requires `JsonValueCodec` implicit value in scope for each type y To use [json4s](https://github.com/json4s/json4s) add the following dependencies to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-json4s" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-json-json4s" % "1.10.13" ``` And one of the implementations: @@ -239,19 +237,19 @@ And one of the implementations: "org.json4s" %% "json4s-jackson" % "4.0.7" ``` -Next, import the package (or extend the `TapirJson4s` trait, see [MyTapir](../mytapir.md) and add `TapirJson4s` instead of `TapirCirceJson`): +Next, import the package (or extend the `TapirJson4s` trait, see [MyTapir](../other/mytapir.md) and add `TapirJson4s` instead of `TapirCirceJson`): ```scala -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 @@ -259,12 +257,12 @@ implicit val formats: Formats = org.json4s.jackson.Serialization.formats(NoTypeH To use [zio-json](https://github.com/zio/zio-json), add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "1.10.13" ``` -Next, import the package (or extend the `TapirJsonZio` trait, see [MyTapir](../mytapir.md) and add `TapirJsonZio` instead of `TapirCirceJson`): +Next, import the package (or extend the `TapirJsonZio` trait, see [MyTapir](../other/mytapir.md) and add `TapirJsonZio` instead of `TapirCirceJson`): ```scala -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 -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/generated-doc/out/endpoint/oneof.md b/generated-doc/out/endpoint/oneof.md index 3baa38247e..d3846c854d 100644 --- a/generated-doc/out/endpoint/oneof.md +++ b/generated-doc/out/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 -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) @@ -89,15 +89,18 @@ val baseEndpoint = endpoint.errorOut( oneOfVariant(StatusCode.InternalServerError, jsonBody[Left[ServerError, UserError]].description("unauthorized")), ) ) -// error: Type scala.util.Right[repl.MdocSession.MdocApp.ServerError,repl.MdocSession.MdocApp.NotFound] is not the same as its erasure. Using a runtime-class-based check it won't be possible to verify that the input matches the desired type. Use other methods to match the input to the appropriate variant instead. -// oneOfVariantValueMatcher(StatusCode.NotFound, jsonBody[Right[ServerError, NotFound]].description("not found")) { -// ^ -// error: Type scala.util.Right[repl.MdocSession.MdocApp.ServerError,repl.MdocSession.MdocApp.BadRequest] is not the same as its erasure. Using a runtime-class-based check it won't be possible to verify that the input matches the desired type. Use other methods to match the input to the appropriate variant instead. -// oneOfVariantValueMatcher(StatusCode.BadRequest, jsonBody[Right[ServerError, BadRequest]].description("unauthorized")) { -// ^ -// error: Type scala.util.Left[repl.MdocSession.MdocApp.ServerError,repl.MdocSession.MdocApp.UserError] is not the same as its erasure. Using a runtime-class-based check it won't be possible to verify that the input matches the desired type. Use other methods to match the input to the appropriate variant instead. -// oneOfVariantValueMatcher(StatusCode.InternalServerError, jsonBody[Left[ServerError, UserError]].description("unauthorized")) { -// ^ +// error: +// Type scala.util.Right[repl.MdocSession.MdocApp.ServerError, repl.MdocSession.MdocApp.NotFound], AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class util)),class Right),List(TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class repl)),module class MdocSession$)),module class MdocApp$)),class ServerError), TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class repl)),module class MdocSession$)),module class MdocApp$)),class NotFound))) is not the same as its erasure. Using a runtime-class-based check it won't be possible to verify that the input matches the desired type. Use other methods to match the input to the appropriate variant instead. +// oneOfVariant(StatusCode.NotFound, jsonBody[Right[ServerError, NotFound]].description("not found")), +// ^ +// error: +// Type scala.util.Right[repl.MdocSession.MdocApp.ServerError, repl.MdocSession.MdocApp.BadRequest], AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class util)),class Right),List(TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class repl)),module class MdocSession$)),module class MdocApp$)),class ServerError), TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class repl)),module class MdocSession$)),module class MdocApp$)),class BadRequest))) is not the same as its erasure. Using a runtime-class-based check it won't be possible to verify that the input matches the desired type. Use other methods to match the input to the appropriate variant instead. +// oneOfVariant(StatusCode.BadRequest, jsonBody[Right[ServerError, BadRequest]].description("unauthorized")), +// ^ +// error: +// Type scala.util.Left[repl.MdocSession.MdocApp.ServerError, repl.MdocSession.MdocApp.UserError], AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class util)),class Left),List(TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class repl)),module class MdocSession$)),module class MdocApp$)),class ServerError), TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class repl)),module class MdocSession$)),module class MdocApp$)),trait UserError))) is not the same as its erasure. Using a runtime-class-based check it won't be possible to verify that the input matches the desired type. Use other methods to match the input to the appropriate variant instead. +// oneOfVariant(StatusCode.InternalServerError, jsonBody[Left[ServerError, UserError]].description("unauthorized")), +// ^ ``` The solution is therefore to handwrite a function checking that a value (of type `Any`) is of the correct type: @@ -157,7 +160,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 -import sttp.tapir._ +import sttp.tapir.* trait DomainException { def help: String @@ -190,10 +193,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 -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/generated-doc/out/endpoint/pickler.md b/generated-doc/out/endpoint/pickler.md index 28b9f53c2a..8d83ad940c 100644 --- a/generated-doc/out/endpoint/pickler.md +++ b/generated-doc/out/endpoint/pickler.md @@ -9,7 +9,7 @@ In [other](json.md) tapir-JSON integrations, you have to keep the `Schema` (whic To use pickler, add the following dependency to your project: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-json-pickler" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-json-pickler" % "1.10.13" ``` Please note that it is available only for Scala 3 and Scala.JS 3. @@ -20,6 +20,7 @@ A pickler can be derived directly using `Pickler.derived[T]`. This will derive b ```scala 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 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 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 +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 +import sttp.tapir.json.pickler.PicklerConfiguration // encodes a case object as { "$type": "MyType" } given PicklerConfiguration = PicklerConfiguration.default @@ -106,31 +107,31 @@ 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 +import sttp.tapir.json.pickler.PicklerConfiguration // encodes a case object as { "who_am_i": "full.pkg.path.MyType" } given customConfiguration: PicklerConfiguration = PicklerConfiguration .default .withDiscriminator("who_am_i") - .withFullDiscriminatorValues + .withFullDiscriminatorValues ``` The discriminator will be added as a field to all coproduct child codecs and schemas, if it’s not yet present. The schema of the added field will always be a Schema.string. Finally, the mapping between the discriminator field values and the child schemas will be generated using `Configuration.toDiscriminatorValue(childSchemaName)`. 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 +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: @@ -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/generated-doc/out/endpoint/schemas.md b/generated-doc/out/endpoint/schemas.md index 02d606ca51..bd9d635af4 100644 --- a/generated-doc/out/endpoint/schemas.md +++ b/generated-doc/out/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 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 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 @@ -120,7 +119,7 @@ the union type, as it's not possible to generate a runtime check for the generic ### Derivation for string-based constant union types e.g. `type AorB = "a" | "b"` -See [enumerations](enumerations.md#scala-3-string-based-constant-union-types-to-enum) on how to use string-based unions of constant types as enums. +See [enumerations](enumerations.html#scala-3-string-based-constant-union-types-to-enum) on how to use string-based unions of constant types as enums. ## Configuring derivation @@ -131,8 +130,7 @@ representation is described in documentation: ```scala 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 -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 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 -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 -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 -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 -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/generated-doc/out/endpoint/security.md b/generated-doc/out/endpoint/security.md index 08207affbc..d522c227fa 100644 --- a/generated-doc/out/endpoint/security.md +++ b/generated-doc/out/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 @@ -31,7 +31,7 @@ base64-encoded username/password combination, use: `basic[UsernamePassword]`. as a string, use: `bearer[String]`. * `auth.oauth2.authorizationCode(authorizationUrl, scopes, tokenUrl, refreshUrl): EndpointInput[String]`: creates an OAuth2 authorization using authorization code - sign in using an auth service (for documentation, requires defining also -the `oauth2-redirect.html`, see [Generating OpenAPI documentation](..docs/openapi.md)) +the `oauth2-redirect.html`, see [Generating OpenAPI documentation](../docs/openapi.md)) ## Authentication challenges @@ -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 -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/generated-doc/out/endpoint/static.md b/generated-doc/out/endpoint/static.md index 878cfc070c..597266f338 100644 --- a/generated-doc/out/endpoint/static.md +++ b/generated-doc/out/endpoint/static.md @@ -11,7 +11,7 @@ the API documentation of the old static content API, switch documentation to an In order to use static content endpoints, add the module to your dependencies: ```scala -"com.softwaremill.sttp.tapir" %% "tapir-files" % "1.10.12" +"com.softwaremill.sttp.tapir" %% "tapir-files" % "1.10.13" ``` ## Files @@ -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 -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 -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 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 -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/generated-doc/out/endpoint/streaming.md b/generated-doc/out/endpoint/streaming.md index 3bc9b7e1cf..703f7339d0 100644 --- a/generated-doc/out/endpoint/streaming.md +++ b/generated-doc/out/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 -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/generated-doc/out/endpoint/validation.md b/generated-doc/out/endpoint/validation.md index 12c62f2648..9f778e670f 100644 --- a/generated-doc/out/endpoint/validation.md +++ b/generated-doc/out/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 -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 -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 -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 -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/generated-doc/out/endpoint/websockets.md b/generated-doc/out/endpoint/websockets.md index 36a85e93ad..a627691ddb 100644 --- a/generated-doc/out/endpoint/websockets.md +++ b/generated-doc/out/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 -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 -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/generated-doc/out/endpoint/xml.md b/generated-doc/out/endpoint/xml.md index 4ad792cda0..3720817958 100644 --- a/generated-doc/out/endpoint/xml.md +++ b/generated-doc/out/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/generated-doc/out/examples.md b/generated-doc/out/examples.md index 04090a8a26..be8151896a 100644 --- a/generated-doc/out/examples.md +++ b/generated-doc/out/examples.md @@ -1,35 +1,18 @@ -# Examples +# Examples by category -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 Tapir repository contains a number of how-to guides. If you're missing an example for your use-case, please let us +know by [reporting an issue](https://github.com/softwaremill/tapir)! -## Generate a tapir project +Each example is fully self-contained and can be run using [scala-cli](https://scala-cli.virtuslab.org) (you just need +to copy the content of the file, apart from scala-cli, no additional setup is required!). Hopefully this will make +experimenting with Tapir as frictionless as possible! -You can generate a simple tapir-based project using chosen features, build tool and effect system using [adopt-tapir](https://adopt-tapir.softwaremill.com). +Examples are tagged with the stack being used (Direct-style, cats-effect, ZIO, Future), server implementation, generated +documentation format etc. -Alternatively, you can generate a stub of a tapir-based application directly from the command line with `sbt new softwaremill/tapir.g8`. +```{eval-rst} +.. include:: includes/examples_list.md + :parser: markdown +``` -## Third-party examples -* http4s interpreter: [todo-backend](https://github.com/lolgab/snunit-tapir-example) -* quickstart using http4s: [a gitter8 template](https://codeberg.org/wegtam/http4s-tapir.g8). A new project can be created using: `sbt new https://codeberg.org/wegtam/http4s-tapir.g8.git` -* Scala Native application, [using Nginx Unit](https://github.com/lolgab/snunit-tapir-example). - -## Blogs, articles - -* [Migrating from Akka HTTP to tapir](https://softwaremill.com/migrating-from-akka-http-to-tapir/) -* [Benchmarking Tapir: Part 1](https://softwaremill.com/benchmarking-tapir-part-1/) -* [Benchmarking Tapir: Part 2](https://softwaremill.com/benchmarking-tapir-part-2/) -* [Tapir 1.0 released](https://softwaremill.com/tapir-1-0-released/) -* [Security improvements in tapir 0.19](https://softwaremill.com/security-improvements-in-tapir-0-19/) -* [Tapir serverless: a proof of concept](https://blog.softwaremill.com/tapir-serverless-a-proof-of-concept-6b8c9de4d396) -* [Designing tapir's WebSockets support](https://blog.softwaremill.com/designing-tapirs-websockets-support-ff1573166368) -* [Three easy endpoints](https://blog.softwaremill.com/three-easy-endpoints-a6cbd52b0a6e) -* [tAPIr's Endpoint meets ZIO's IO](https://blog.softwaremill.com/tapirs-endpoint-meets-zio-s-io-3278099c5e10) -* [Describe, then interpret: HTTP endpoints using tapir](https://blog.softwaremill.com/describe-then-interpret-http-endpoints-using-tapir-ac139ba565b0) -* [Functional pancakes](https://blog.softwaremill.com/functional-pancakes-cf70023f0dcb) - -## Videos - -* [ScalaLove 2020: Your HTTP endpoints are data, as well!](https://www.youtube.com/watch?v=yuQNgZgSFIc&t=944s) -* [Scalar 2020: A Functional Scala Stack For 2020](https://www.youtube.com/watch?v=DGlkap5kzGU) -* [ScalaWorld 2019: Designing Programmer-Friendly APIs](https://www.youtube.com/watch?v=I3loMuHnYqw) diff --git a/generated-doc/out/external.md b/generated-doc/out/external.md new file mode 100644 index 0000000000..5a9a07188f --- /dev/null +++ b/generated-doc/out/external.md @@ -0,0 +1,33 @@ +# Articles, videos, other examples + +## Generate a tapir project + +You can generate a simple tapir-based project using chosen features, build tool and effect system using [adopt-tapir](https://adopt-tapir.softwaremill.com). + +Alternatively, you can generate a stub of a tapir-based application directly from the command line with `sbt new softwaremill/tapir.g8`. + +## Third-party examples + +* http4s interpreter: [todo-backend](https://github.com/lolgab/snunit-tapir-example) +* quickstart using http4s: [a gitter8 template](https://codeberg.org/wegtam/http4s-tapir.g8). A new project can be created using: `sbt new https://codeberg.org/wegtam/http4s-tapir.g8.git` +* Scala Native application, [using Nginx Unit](https://github.com/lolgab/snunit-tapir-example). + +## Blogs, articles + +* [Migrating from Akka HTTP to tapir](https://softwaremill.com/migrating-from-akka-http-to-tapir/) +* [Benchmarking Tapir: Part 1](https://softwaremill.com/benchmarking-tapir-part-1/) +* [Benchmarking Tapir: Part 2](https://softwaremill.com/benchmarking-tapir-part-2/) +* [Tapir 1.0 released](https://softwaremill.com/tapir-1-0-released/) +* [Security improvements in tapir 0.19](https://softwaremill.com/security-improvements-in-tapir-0-19/) +* [Tapir serverless: a proof of concept](https://blog.softwaremill.com/tapir-serverless-a-proof-of-concept-6b8c9de4d396) +* [Designing tapir's WebSockets support](https://blog.softwaremill.com/designing-tapirs-websockets-support-ff1573166368) +* [Three easy endpoints](https://blog.softwaremill.com/three-easy-endpoints-a6cbd52b0a6e) +* [tAPIr's Endpoint meets ZIO's IO](https://blog.softwaremill.com/tapirs-endpoint-meets-zio-s-io-3278099c5e10) +* [Describe, then interpret: HTTP endpoints using tapir](https://blog.softwaremill.com/describe-then-interpret-http-endpoints-using-tapir-ac139ba565b0) +* [Functional pancakes](https://blog.softwaremill.com/functional-pancakes-cf70023f0dcb) + +## Videos + +* [ScalaLove 2020: Your HTTP endpoints are data, as well!](https://www.youtube.com/watch?v=yuQNgZgSFIc&t=944s) +* [Scalar 2020: A Functional Scala Stack For 2020](https://www.youtube.com/watch?v=DGlkap5kzGU) +* [ScalaWorld 2019: Designing Programmer-Friendly APIs](https://www.youtube.com/watch?v=I3loMuHnYqw) diff --git a/generated-doc/out/generate.md b/generated-doc/out/generate.md new file mode 100644 index 0000000000..29302de31a --- /dev/null +++ b/generated-doc/out/generate.md @@ -0,0 +1,21 @@ +# Generate a Tapir project + +Not sure how to start? + +We recommend the defaults below (Direct-style stack & Netty server); this requires Java 21+. Otherwise, +you might also try the Future stack + Netty, which works on Java 11+. + +If you'd like to include a JSON endpoint, using the [jsoniter](https://github.com/plokhotnyuk/jsoniter-scala) library +might a good choice! + +```{eval-rst} +.. raw:: html + + +``` diff --git a/generated-doc/out/generator/sbt-openapi-codegen.md b/generated-doc/out/generator/sbt-openapi-codegen.md index b67442d70f..ff4bbba9ed 100644 --- a/generated-doc/out/generator/sbt-openapi-codegen.md +++ b/generated-doc/out/generator/sbt-openapi-codegen.md @@ -9,7 +9,7 @@ This is a really early alpha implementation. Add the sbt plugin to the `project/plugins.sbt`: ```scala -addSbtPlugin("com.softwaremill.sttp.tapir" % "sbt-openapi-codegen" % "1.10.12") +addSbtPlugin("com.softwaremill.sttp.tapir" % "sbt-openapi-codegen" % "1.10.13") ``` Enable the plugin for your project in the `build.sbt`: @@ -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") ``` @@ -89,8 +89,9 @@ If `openapiUseHeadTagForObjectName = true`, then the `GET /foo` and `GET /bar` `Baz.scala` file, containing a single `object Baz` with those endpoint definitions; the `PUT /foo` endpoint, by dint of having no tags, would be output to the `TapirGeneratedEndpoints` file, along with any schema and parameter definitions. -Files can be generated from multiple openapi schemas if `openapiAdditionalPackages` is configured; for example -```sbt +Files can be generated from multiple openapi schemas if `openapiAdditionalPackages` is configured; for example: + +```scala openapiAdditionalPackages := List( "sttp.tapir.generated.v1" -> baseDirectory.value / "src" / "main" / "resources" / "openapi_v1.yml") ``` diff --git a/generated-doc/out/includes/examples_list.md b/generated-doc/out/includes/examples_list.md new file mode 100644 index 0000000000..6e62dcbe7d --- /dev/null +++ b/generated-doc/out/includes/examples_list.md @@ -0,0 +1,103 @@ +## Hello, World! + +* [A demo of Tapir's capabilities](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/booksExample.scala) circe sttp3 Swagger UI Future Pekko HTTP +* [A demo of Tapir's capabilities](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/booksPicklerExample.scala) Pickler sttp3 Swagger UI Future Netty +* [Exposing an endpoint using the Armeria server](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/helloWorldArmeriaServer.scala) Future Armeria +* [Exposing an endpoint using the Netty server (Direct-style variant)](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/helloWorldNettySyncServer.scala) Direct Netty +* [Exposing an endpoint using the Netty server (Future variant)](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/helloWorldNettyFutureServer.scala) Future Netty +* [Exposing an endpoint using the Netty server (cats-effect variant)](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala) cats-effect Netty +* [Exposing an endpoint using the Pekko HTTP server](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/helloWorldPekkoServer.scala) Future Pekko HTTP +* [Exposing an endpoint using the ZIO HTTP server](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/HelloWorldZioHttpServer.scala) ZIO ZIO JSON ZIO HTTP +* [Exposing an endpoint using the ZIO HTTP server](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala) Swagger UI ZIO circe zio-http +* [Exposing an endpoint using the built-in JDK HTTP server](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/helloWorldJdkHttpServer.scala) Direct JDK Http +* [Exposing an endpoint using the http4s server](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala) Future http4s +* [Exposing an endpoint using the http4s server](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala) Swagger UI ZIO circe http4s +* [Exposing an endpoint, defined with ZIO and depending on services in the environment, using the http4s server](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala) Swagger UI ZIO circe http4s +* [Extending a base endpoint (which has the security logic provided), with server logic](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala) ZIO http4s + +## Client interpreter + +* [Interpreting an endpoint as an http4s client](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/client/Http4sClientExample.scala) circe cats-effect + +## Custom types + +* [A demo of Tapir's capabilities using semi-auto derivation](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/custom_types/booksExampleSemiauto.scala) circe sttp3 Swagger UI Future Pekko HTTP +* [Handling comma-separated query parameters](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/custom_types/commaSeparatedQueryParameter.scala) Swagger UI Direct Netty +* [Mapping a sealed trait hierarchy to JSON using a discriminator](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/custom_types/sealedTraitWithDiscriminator.scala) circe Swagger UI Direct Netty +* [Supporting custom types, when used in query or path parameters, as well as part of JSON bodies](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/custom_types/EndpointWithCustomTypes.scala) circe + +## Error handling + +* [Customising errors that are reported on decode failures (e.g. invalid or missing query parameter)](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/errors/customErrorsOnDecodeFailurePekkoServer.scala) Future Pekko HTTP +* [Error and successful outputs](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/errors/errorOutputsPekkoServer.scala) Future circe Pekko HTTP +* [Error reporting provided by Iron type refinements](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala) circe cats-effect Netty +* [Extending a base secured endpoint with error variants, using union types](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/errors/ErrorUnionTypesHttp4sServer.scala) circe cats-effect http4s + +## Logging + +* [Logging using a correlation id](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/logging/ZioLoggingWithCorrelationIdNettyServer.scala) ZIO Netty + +## Multipart + +* [Uploading a multipart form, with text and file parts](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/multipart/multipartFormUploadPekkoServer.scala) Future Pekko HTTP + +## Observability + +* [Reporting DataDog metrics](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/observability/datadogMetricsExample.scala) Future circe Netty +* [Reporting OpenTelemetry metrics](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/observability/openTelemetryMetricsExample.scala) Future circe Netty +* [Reporting Prometheus metrics](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/observability/ZioMetricsExample.scala) ZIO ZIO HTTP +* [Reporting Prometheus metrics](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/observability/prometheusMetricsExample.scala) Future circe Netty + +## OpenAPI documentation + +* [Adding OpenAPI documentation extensions](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/openapi/openapiExtensions.scala) circe +* [Documenting multiple endpoints](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/openapi/MultipleEndpointsDocumentationHttp4sServer.scala) Swagger UI cats-effect circe http4s +* [Documenting multiple endpoints](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/openapi/multipleEndpointsDocumentationPekkoServer.scala) Swagger UI Future circe Pekko HTTP +* [Exposing documentation using ReDoc](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/openapi/RedocZioHttpServer.scala) ReDoc ZIO circe ZIO HTTP +* [Exposing documentation using ReDoc](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/openapi/RedocContextPathHttp4sServer.scala) ReDoc cats-effect http4s +* [Securing Swagger UI using OAuth 2](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/openapi/swaggerUIOAuth2PekkoServer.scala) Swagger UI Future Pekko HTTP + +## Schemas + +* [Customising a derived schema, using annotations, and using implicits](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/schema/customisingSchemas.scala) Swagger UI Future circe Netty + +## Security + +* [CORS interceptor](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/security/corsInterceptorPekkoServer.scala) Future Pekko HTTP +* [HTTP basic authentication](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/security/basicAuthenticationPekkoServer.scala) Future Pekko HTTP +* [Interceptor verifying externally added security credentials](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/security/externalSecurityInterceptor.scala) Future Netty +* [Login using OAuth2, authorization code flow](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/security/OAuth2GithubHttp4sServer.scala) cats-effect circe http4s +* [Separating security and server logic, with a reusable base endpoint](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/security/serverSecurityLogicPekko.scala) Future Pekko HTTP +* [Separating security and server logic, with a reusable base endpoint, accepting & refreshing credentials via cookies](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/security/serverSecurityLogicRefreshCookiesPekko.scala) Future Pekko HTTP +* [Separating security and server logic, with a reusable base endpoint, accepting & refreshing credentials via cookies](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicZio.scala) ZIO ZIO HTTP + +## Static content + +* [Serving static files from a directory](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/static_content/staticContentFromFilesNettyServer.scala) Direct Netty +* [Serving static files from a directory, with range requests](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/static_content/staticContentFromFilesPekkoServer.scala) Future Pekko HTTP +* [Serving static files from resources](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/static_content/staticContentFromResourcesPekkoServer.scala) Future Pekko HTTP +* [Serving static files secured with a bearer token](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/static_content/staticContentSecurePekkoServer.scala) Future Pekko HTTP + +## Status code + +* [Serving static files from a directory](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/status_code/statusCodeNettyServer.scala) Direct Netty + +## Streaming + +* [Proxy requests, handling bodies as fs2 streams](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/streaming/ProxyHttp4sFs2Server.scala) cats-effect http4s +* [Stream response as a Pekko stream](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/streaming/streamingPekkoServer.scala) Future Pekko HTTP +* [Stream response as a ZIO stream](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyZioServer.scala) ZIO Netty +* [Stream response as a ZIO stream](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/streaming/StreamingZioHttpServer.scala) ZIO ZIO HTTP +* [Stream response as an fs2 stream](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala) cats-effect http4s +* [Stream response as an fs2 stream](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala) cats-effect Netty + +## Testing + +* [Test endpoints using the MockServer client](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/testing/SttpMockServerClientExample.scala) circe +* [Test endpoints using the TapirStubInterpreter](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/testing/PekkoServerStubInterpreterExample.scala) Future Pekko HTTP + +## WebSocket + +* [Describe and implement a WebSocket endpoint](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketNettySyncServer.scala) Direct Netty +* [Describe and implement a WebSocket endpoint](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/websocket/webSocketPekkoServer.scala) Future Pekko HTTP +* [Describe and implement a WebSocket endpoint](https://github.com/softwaremill/tapir/tree/master//examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketHttp4sServer.scala) AsyncAPI cats-effect circe http4s \ No newline at end of file diff --git a/generated-doc/out/index.md b/generated-doc/out/index.md index cba78b3e12..5c520c636c 100644 --- a/generated-doc/out/index.md +++ b/generated-doc/out/index.md @@ -1,128 +1,65 @@ # tapirDeclarative, type-safe web endpoints library.
+Rapid development of self-documenting APIs