=============== LIBRARY RULES =============== From library maintainers: - When defining Tapir endpoints, always start with the base `endpoint` value and chain methods to build input/output specifications. - Import `sttp.tapir.*` to bring all necessary Tapir functionality into scope for endpoint definitions. - Use `PublicEndpoint[I, E, O, R]` type alias for endpoints without security inputs instead of the full `Endpoint[Unit, I, E, O, R]` type. - Always provide both success and error outputs for endpoints, with error outputs defined using `.errorOut()` methods. - For JSON endpoints, add the appropriate JSON library dependency (circe, play-json, etc.) and import the corresponding package like `sttp.tapir.json.circe.*`. - Use `jsonBody[T]` for JSON request/response bodies, ensuring both a JSON codec (Encoder/Decoder) and Schema[T] are in scope. - Server logic functions should return `F[Either[E, O]]` where F is the effect type, E is the error type, and O is the success type. - When using direct-style, server logic functions should return `Either[E, O]`, and the logic should be provided using .handle methods. - When using `.serverLogicRecoverErrors()`, the error type E must extend Throwable to automatically recover from failed effects. - Status codes default to 200 for success and 400 for errors, but can be customized using status code outputs like `.out(statusCode(StatusCode.Created))`. - Use `.serverSecurityLogic()` followed by `.serverLogic()` for endpoints with authentication, where security logic returns Either[E, User] and main logic uses the User type. - Tapir mappings are always bidirectional, so use `.mapTo[CaseClass]` to automatically map tuples to case classes for both input and output. - Import `sttp.tapir.generic.auto.*` to enable automatic schema derivation for case classes used in endpoint definitions. - Use `infallibleEndpoint` instead of `endpoint` when you want to indicate that no errors can occur in the endpoint. - For file uploads and multipart forms, use specific inputs like `multipartBody` and ensure proper codecs are defined. - When generating OpenAPI documentation, use `SwaggerInterpreter()` or `OpenAPIDocsInterpreter()` to create documentation from endpoint definitions. - Use validation methods like `.validate()` on inputs to add custom validation logic that will be reflected in generated documentation. ### Start Armeria Server with ZIO Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/armeria.md Example of starting an Armeria HTTP server using Tapir with ZIO. It shows how to integrate ZIO's runtime and manage server lifecycle. ```scala import com.linecorp.armeria.server.Server import sttp.tapir.* import sttp.tapir.server.armeria.zio.ArmeriaZioServerInterpreter import sttp.tapir.ztapir.* import zio.{ExitCode, Runtime, UIO, URIO, ZIO, ZIOAppDefault} import java.util.concurrent.CompletableFuture object Main extends ZIOAppDefault: override def run: URIO[Any, ExitCode] = given Runtime[Any] = Runtime.default val tapirEndpoint: PublicEndpoint[String, Unit, String, Any] = ??? def logic(key: String): UIO[String] = ??? val s = ZIO.fromCompletableFuture { val tapirService = ArmeriaZioServerInterpreter().toService(tapirEndpoint.zServerLogic(logic)) val server = Server .builder() .service(tapirService) .build() server.start().thenApply[Server](_ => server) } ZIO.scoped(ZIO.acquireRelease(s)( server => ZIO.fromCompletableFuture(server.closeAsync().asInstanceOf[CompletableFuture[Unit]]).orDie) *> ZIO.never).exitCode ``` -------------------------------- ### Start Play Server Manually Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/play.md Example of creating and starting a Play server manually using Netty, binding it to a specific route. ```scala import play.core.server.* import play.api.routing.Router.Routes val aRoute: Routes = ??? // JVM entry point that starts the HTTP server - uncomment @main to run /* @main */ def playServer(): Unit = val playConfig = ServerConfig( port = sys.props.get("http.port").map(_.toInt).orElse(Some(9000)) ) NettyServer.fromRouterWithComponents(playConfig) { components => aRoute } ``` -------------------------------- ### Exposing Endpoints with NettySyncServer Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/netty.md Example of exposing a simple 'hello world' endpoint using NettySyncServer and starting the server. This uses direct-style with Virtual Threads. ```scala import sttp.tapir.* import sttp.tapir.server.netty.sync.NettySyncServer val helloWorld = endpoint .get .in("hello").in(query[String]("name")) .out(stringBody) .handleSuccess(name => s"Hello, $name!") NettySyncServer().addEndpoint(helloWorld).startAndWait() ``` -------------------------------- ### Simple Vert.x Cats Effect Server Example Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/vertx.md A basic example demonstrating how to start an HTTP server with a single route using Vert.x and Cats Effect. It sets up an endpoint, a handler, and attaches it to the Vert.x router. ```scala import cats.effect.* import cats.effect.std.Dispatcher import io.vertx.core.Vertx import io.vertx.ext.web.Router import sttp.tapir.* import sttp.tapir.server.vertx.cats.VertxCatsServerInterpreter import sttp.tapir.server.vertx.cats.VertxCatsServerInterpreter.* object App extends IOApp: val responseEndpoint: PublicEndpoint[String, Unit, String, Any] = endpoint .in("response") .in(query[String]("key")) .out(plainBody[String]) def handler(req: String): IO[Either[Unit, String]] = IO.pure(Right(req)) override def run(args: List[String]): IO[ExitCode] = Dispatcher[IO] .flatMap { dispatcher => Resource .make( IO.delay { val vertx = Vertx.vertx() val server = vertx.createHttpServer() val router = Router.router(vertx) val attach = VertxCatsServerInterpreter[IO](dispatcher).route(responseEndpoint.serverLogic(handler)) attach(router) server.requestHandler(router).listen(8080) } .flatMap(_.asF[IO]) )({ server => IO.delay(server.close).flatMap(_.asF[IO].void) }) } .use(_ => IO.never) ``` -------------------------------- ### Start Armeria Server with Cats Effect Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/armeria.md Example of starting an Armeria HTTP server using Tapir with Cats Effect. It demonstrates setting up a basic endpoint and handling server lifecycle. ```scala import sttp.tapir.* import sttp.tapir.server.armeria.cats.ArmeriaCatsServerInterpreter import cats.effect.* import cats.effect.std.Dispatcher import com.linecorp.armeria.server.Server import java.util.concurrent.CompletableFuture object Main extends IOApp: override def run(args: List[String]): IO[ExitCode] = val tapirEndpoint: PublicEndpoint[String, Unit, String, Any] = ??? def logic(req: String): IO[Either[Unit, String]] = ??? Dispatcher[IO] .flatMap { dispatcher => Resource .make( IO.async_[Server] { cb => val tapirService = ArmeriaCatsServerInterpreter[IO](dispatcher).toService(tapirEndpoint.serverLogic(logic)) val server = Server .builder() .service(tapirService) .build() server.start().handle[Unit] { case (_, null) => cb(Right(server)) case (_, cause) => cb(Left(cause)) } } ) ({ server => IO.fromCompletableFuture(IO(server.closeAsync().asInstanceOf[CompletableFuture[Unit]])) }) } .use(_ => IO.never) ``` -------------------------------- ### Simple Vert.x ZIO Server Example Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/vertx.md A straightforward example of setting up a Vert.x HTTP server with a single route using ZIO. It defines an endpoint and attaches a ZIO-based server logic. ```scala import io.vertx.core.Vertx import io.vertx.ext.web.Router import sttp.tapir.{plainBody, query} import sttp.tapir.ztapir.* import sttp.tapir.server.vertx.zio.VertxZioServerInterpreter import sttp.tapir.server.vertx.zio.VertxZioServerInterpreter.* import zio.* object Short extends ZIOAppDefault: override implicit val runtime = zio.Runtime.default val responseEndpoint = endpoint .in("response") .in(query[String]("key")) .out(plainBody[String]) val attach = VertxZioServerInterpreter().route(responseEndpoint.zServerLogic { key => ZIO.succeed(key) }) override def run = ZIO.scoped( ZIO .acquireRelease( ZIO .attempt { val vertx = Vertx.vertx() val server = vertx.createHttpServer() val router = Router.router(vertx) attach(router) server.requestHandler(router).listen(8080) } .flatMap(_.asRIO) ) { server => ZIO.attempt(server.close()).flatMap(_.asRIO).orDie } *> ZIO.never ) ``` -------------------------------- ### Define Endpoints and Start Server Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/tutorials/02_openapi_docs.md Sets up two tapir endpoints and starts a Netty server to expose them. Includes dependencies for core, Netty server, and Swagger UI bundle. ```scala //> using dep com.softwaremill.sttp.tapir::tapir-core:1.13.18 //> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.13.18 //> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.13.18 import sttp.tapir.* import sttp.tapir.server.netty.sync.NettySyncServer @main def tapirDocs(): Unit = val e1 = endpoint .get.in("hello" / "world").in(query[String]("name")) .out(stringBody) .handleSuccess(name => s"Hello, $name!") val e2 = endpoint .post.in("double").in(stringBody) .errorOut(stringBody) .out(stringBody) .handle { s => s.toIntOption.fold(Left(s"$s is not a number"))(n => Right((n*2).toString)) } NettySyncServer().port(8080) .addEndpoints(List(e1, e2)) .startAndWait() ``` -------------------------------- ### Initialize ZioMetrics Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/observability.md Example of creating a ZioMetrics instance and obtaining the metrics interceptor for use with ZIO-based servers. ```scala import sttp.tapir.server.metrics.zio.ZioMetrics import sttp.tapir.server.interceptor.metrics.MetricsRequestInterceptor import zio.{Task, ZIO} val metrics: ZioMetrics[Task] = ZioMetrics.default[Task]() val metricsInterceptor: MetricsRequestInterceptor[Task] = metrics.metricsInterceptor() ``` -------------------------------- ### Start Netty Server with Domain Socket Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/netty.md Configure and start a Netty server using a domain socket instead of a TCP port. Ensure the necessary imports are present. ```scala import sttp.tapir.* import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureDomainSocketBinding} import java.nio.file.Paths import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import io.netty.channel.unix.DomainSocketAddress val serverBinding: Future[NettyFutureDomainSocketBinding] = NettyFutureServer().addEndpoint( endpoint.get.in("hello").in(query[String]("name")).out(stringBody).serverLogic(name => Future.successful[Either[Unit, String]](Right(s"Hello, $name!"))) ) .startUsingDomainSocket(Paths.get(System.getProperty("java.io.tmpdir"), "hello")) ``` -------------------------------- ### Define Basic Endpoint with Method and Path Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/tutorials/01_hello_world.md Define a basic GET endpoint with a '/hello/world' path. This snippet shows how to import tapir and use the `endpoint` object to start defining an endpoint. ```scala //> using dep com.softwaremill.sttp.tapir::tapir-core:1.13.18 //> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.13.18 import sttp.tapir.* @main def helloWorldTapir(): Unit = val helloWorldEndpoint = endpoint .get .in("hello" / "world") println(helloWorldEndpoint.show) ``` -------------------------------- ### Start Endpoint Definition Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/quickstart.md Begin defining an endpoint by typing 'endpoint.' and using auto-complete to discover available methods. ```scala endpoint. ``` -------------------------------- ### Mount tapir endpoint as a Vert.x route Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/vertx.md This example shows how to create a Vert.x HTTP server, define an endpoint, attach it to a router, and start the server. Use `route` for non-blocking handlers and `blockingRoute` for blocking handlers. ```scala import sttp.tapir.* import sttp.tapir.server.vertx.VertxFutureServerInterpreter import sttp.tapir.server.vertx.VertxFutureServerInterpreter.* import io.vertx.core.Vertx import io.vertx.ext.web.* import scala.concurrent.{Await, Future} import scala.concurrent.duration.* // JVM entry point that starts the HTTP server - uncomment @main to run /* @main */ def vertxServer(): Unit = val vertx = Vertx.vertx() val server = vertx.createHttpServer() val router = Router.router(vertx) val anEndpoint: PublicEndpoint[(String, Int), Unit, String, Any] = ??? // your definition here def logic(s: String, i: Int): Future[Either[Unit, String]] = ??? // your logic here val attach = VertxFutureServerInterpreter().route(anEndpoint.serverLogic((logic _).tupled)) attach(router) // your endpoint is now attached to the router, and the route has been created Await.result(server.requestHandler(router).listen(9000).asScala, Duration.Inf) ``` -------------------------------- ### Circe Printer Configuration Example Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/endpoint/json.md Illustrates how to configure Circe's printer to omit null values and pretty-print JSON output. ```scala import io.circe.* Json.obj( "key1" -> Json.fromString("present"), "key2" -> Json.Null ) ``` -------------------------------- ### Expose OpenAPI Documentation with Swagger UI (Akka-HTTP Example) Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/docs/openapi.md Integrate Swagger UI by converting endpoints to YAML and passing the result to the SwaggerUI server endpoint definition. This example uses Netty and Future. ```scala 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 import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global val myEndpoints: Seq[AnyEndpoint] = ??? val docsAsYaml: String = OpenAPIDocsInterpreter().toOpenAPI(myEndpoints, "My App", "1.0").toYaml // add to your netty routes val swaggerUIRoute: FutureRoute = NettyFutureServerInterpreter().toRoute(SwaggerUI[Future](docsAsYaml)) ``` -------------------------------- ### Define Sample Endpoint Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/testing.md Example of defining a JSON endpoint with headers and body for testing. ```scala import sttp.tapir.* import sttp.tapir.generic.auto.* import sttp.tapir.json.circe.* import io.circe.generic.auto.* case class SampleIn(name: String, age: Int) case class SampleOut(greeting: String) val sampleJsonEndpoint = endpoint.post .in("api" / "v1" / "json") .in(header[String]("X-RequestId")) .in(jsonBody[SampleIn]) .errorOut(stringBody) .out(jsonBody[SampleOut]) ``` -------------------------------- ### Initialize DatadogMetrics Client Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/observability.md Example of initializing a StatsDClient for Datadog metrics. Ensure the hostname and port match your Datadog Agent configuration. ```scala import com.timgroup.statsd.{NonBlockingStatsDClientBuilder, StatsDClient} import sttp.tapir.server.metrics.datadog.DatadogMetrics import scala.concurrent.Future val statsdClient: StatsDClient = new NonBlockingStatsDClientBuilder() .hostname("localhost") // Datadog Agent's hostname .port(8125) // Datadog Agent's port (UDP) .build() val metrics = DatadogMetrics.default[Future](statsdClient) ``` -------------------------------- ### Serve Static Files Without a Path Prefix Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/endpoint/static.md Expose files from a directory at the root of your server using `emptyInput` with `staticFilesGetServerEndpoint`. This example serves content from `/var/www` at `http://localhost:8080`. ```scala import sttp.tapir.server.netty.sync.NettySyncServer import sttp.tapir.emptyInput import sttp.tapir.* import sttp.tapir.files.* NettySyncServer() .addEndpoint(staticFilesGetServerEndpoint(emptyInput)("/var/www")) .startAndWait() ``` -------------------------------- ### Overwrite Response Specification Example Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/client/sttp.md Example demonstrating how to overwrite the default response handling using `.response(asStringAlways)`. ```scala import sttp.tapir._ import sttp.tapir.client.sttp4.SttpClientInterpreter import sttp.client4._ SttpClientInterpreter() .toRequest(endpoint.get.in("hello").in(query[String]("name")), Some(uri"http://localhost:8080")) .apply("Ann") .response(asStringAlways) ``` -------------------------------- ### Configure Static File Serving with FileOptions Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/endpoint/static.md Customize static file serving behavior using `FilesOptions`. This example enables gzipped file serving, custom ETag calculation, file filtering, and specifies a default file. ```scala import sttp.model.headers.ETag import sttp.tapir.emptyInput import sttp.tapir.* import sttp.tapir.files.* import sttp.shared.Identity import java.net.URL val customETag: Option[RangeValue] => URL => Option[ETag] = ??? val customFileFilter: List[String] => Boolean = ??? val options: FilesOptions[Identity] = FilesOptions .default[Identity] .withUseGzippedIfAvailable .calculateETag(customETag) .fileFilter(customFileFilter) .defaultFile(List("default.md")) val endpoint = staticFilesGetServerEndpoint[Identity](emptyInput)("/var/www", options) ``` -------------------------------- ### Define Hello World Endpoint with Server Logic Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/tutorials/01_hello_world.md Defines a GET endpoint at "/hello/world" that accepts a "name" query parameter and returns a greeting. The server logic is attached using `.handleSuccess`. ```scala //> using dep com.softwaremill.sttp.tapir::tapir-core:1.13.18 //> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.13.18 import sttp.tapir.* @main def helloWorldTapir(): Unit = val helloWorldEndpoint = endpoint .get .in("hello" / "world") .in(query[String]("name")) .out(stringBody) .handleSuccess(name => s"Hello, $name!") println(helloWorldEndpoint.show) ``` -------------------------------- ### Define a Simple GET Endpoint with a Query Parameter Source: https://github.com/softwaremill/tapir/blob/master/README.md This snippet defines a basic GET endpoint that expects a 'name' query parameter and returns a greeting string. It demonstrates the core syntax for defining inputs and outputs. ```scala endpoint .get.in("hello").in(query[String]("name")) .out(stringBody) .handleSuccess(name => s"Hello, $name!") ``` -------------------------------- ### Expose Hello World Endpoint with Netty Server Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/tutorials/01_hello_world.md Exposes the previously defined "Hello World" endpoint using NettySyncServer, binding it to port 8080. The server starts and waits for incoming connections. ```scala //> using dep com.softwaremill.sttp.tapir::tapir-core:1.13.18 //> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.13.18 import sttp.tapir.* import sttp.tapir.server.netty.sync.NettySyncServer @main def helloWorldTapir(): Unit = val helloWorldEndpoint = endpoint .get .in("hello" / "world") .in(query[String]("name")) .out(stringBody) .handleSuccess(name => s"Hello, $name!") NettySyncServer() .port(8080) .addEndpoint(helloWorldEndpoint) .startAndWait() ``` -------------------------------- ### Example: Making a Request with Play Client Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/client/play.md Demonstrates how to use PlayClientInterpreter to create a request and a response parser. The response parser might throw an exception on decoding failures. Ensure you have the necessary imports and a StandaloneWSClient available. ```scala import sttp.tapir.* import sttp.tapir.client.play.PlayClientInterpreter import sttp.capabilities.pekko.PekkoStreams import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import play.api.libs.ws.StandaloneWSClient def example[I, E, O, R >: PekkoStreams](implicit wsClient: StandaloneWSClient): Unit = val e: PublicEndpoint[I, E, O, R] = ??? val inputArgs: I = ??? val (req, responseParser) = PlayClientInterpreter() .toRequestThrowDecodeFailures(e, s"http://localhost:9000") .apply(inputArgs) val result: Future[Either[E, O]] = req .execute() .map(responseParser) ``` -------------------------------- ### Example JSON Object Rendering with Custom Printer Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/endpoint/json.md Demonstrates how a JSON object is rendered with the custom Circe printer that drops null values. ```json {"key1":"present"} ``` -------------------------------- ### Example of Basic Auth Triggering Password Prompt Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/endpoint/security.md When an endpoint is secured with `auth.basic`, browsers will typically display a password prompt to the user if credentials are not provided. ```scala endpoint.get.securityIn("path").securityIn(auth.basic[UsernamePassword]()) ``` -------------------------------- ### Define and Run a Helidon Níma Server with Tapir Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/nima.md This example demonstrates how to define a Tapir endpoint and serve it using Helidon Níma. The endpoint returns 'hello, world!' after a 1-second delay. Níma server requires JDK 21+. ```scala import io.helidon.webserver.WebServer import sttp.tapir.* import sttp.shared.Identity import sttp.tapir.server.nima.NimaServerInterpreter val helloEndpoint = endpoint.get .in("hello") .out(stringBody) .handleSuccess { _ => Thread.sleep(1000) "hello, world!" } val handler = NimaServerInterpreter().toHandler(List(helloEndpoint)) WebServer .builder() .routing { builder => builder.any(handler) () } .port(8080) .build() .start() ``` -------------------------------- ### Define and Serve a Tapir Endpoint with Play 3.0 Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/play.md Example of creating a Tapir endpoint with server logic and converting it to Play 3.0 routes. Ensure you have a Materializer available. ```scala import org.apache.pekko.stream.Materializer import play.api.routing.Router.Routes import sttp.tapir.* import sttp.tapir.server.play.PlayServerInterpreter import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future given Materializer = ??? def countCharacters(s: String): Future[Either[Unit, Int]] = Future(Right[Unit, Int](s.length)) val countCharactersEndpoint: PublicEndpoint[String, Unit, Int, Any] = endpoint.in(stringBody).out(plainBody[Int]) val countCharactersRoutes: Routes = PlayServerInterpreter().toRoutes(countCharactersEndpoint.serverLogic(countCharacters _)) ``` -------------------------------- ### Configure Otel4sMetrics with Http4s Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/observability.md Example of setting up Otel4sMetrics as the metrics interceptor for Http4s server options. Requires a Meter[F] instance from otel4s. ```scala import cats.effect.IO import org.typelevel.otel4s.oteljava.OtelJava import sttp.tapir.server.http4s.Http4sServerInterpreter import sttp.tapir.server.http4s.Http4sServerOptions import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.metrics.otel4s.Otel4sMetrics OtelJava .autoConfigured[IO]() .use { otel4s => otel4s.meterProvider.get("meter-name").flatMap { meter => val endpoints: List[ServerEndpoint[Any, IO]] = ??? val routes = Http4sServerInterpreter[IO]( Http4sServerOptions .customiseInterceptors[IO] .metricsInterceptor(Otel4sMetrics.default(meter).metricsInterceptor()) .options ).toRoutes(endpoints) // start your server ??? } } ``` -------------------------------- ### Prepend Custom Security Logic to Static File Endpoints Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/logic.md Extends static file server endpoints with custom security logic using `prependSecurity`. This example adds a bearer token authentication check before serving files. ```scala import sttp.tapir.* import sttp.tapir.files.* import scala.concurrent.Future import sttp.model.StatusCode val secureFileEndpoints = staticFilesServerEndpoints[Future]("secure")("/home/data") .map(_.prependSecurity(auth.bearer[String](), statusCode(StatusCode.Forbidden)) { token => Future.successful(if (token.startsWith("secret")) Right(()) else Left(())) }) ``` -------------------------------- ### Implement Server Logic and Expose Endpoints Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/tutorials/04_errors.md Implement the server logic for the endpoint, returning `Either[Error, Result]`. This snippet also sets up a Netty server, adds the endpoint and Swagger UI, and starts the server. ```scala //> using dep com.softwaremill.sttp.tapir::tapir-core:1.13.18 //> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.13.18 //> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.13.18 //> using dep com.softwaremill.sttp.tapir::tapir-jsoniter-scala:1.13.18 //> using dep com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros:2.30.1 import com.github.plokhotnyuk.jsoniter_scala.macros.* import sttp.tapir.* import sttp.tapir.json.jsoniter.* import sttp.tapir.server.netty.sync.NettySyncServer import sttp.tapir.swagger.bundle.SwaggerInterpreter import sttp.shared.Identity case class Result(v: Int) derives ConfiguredJsonValueCodec, Schema case class Error(description: String) derives ConfiguredJsonValueCodec, Schema @main def tapirErrors(): Unit = val maybeErrorEndpoint = endpoint.get .in("test") .in(query[Int]("input")) .out(jsonBody[Result]) .errorOut(jsonBody[Error]) .handle { input => if input % 2 == 0 then Right(Result(input/2)) else Left(Error("That's an odd number!")) } val swaggerEndpoints = SwaggerInterpreter() .fromServerEndpoints[Identity](List(maybeErrorEndpoint), "My App", "1.0") NettySyncServer().port(8080) .addEndpoint(maybeErrorEndpoint) .addEndpoints(swaggerEndpoints) .startAndWait() ``` -------------------------------- ### Server Sent Events with Netty Sync Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/netty.md This example shows how to implement Server-Sent Events (SSE) using tapir with the Netty sync server. It defines an endpoint that emits a stream of `ServerSentEvent` objects. ```APIDOC ## Server Sent Events ### tapir-netty-server-sync The interpreter supports [SSE (Server Sent Events)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). For example, to define an endpoint that returns event stream: ```scala import ox.flow.Flow import sttp.model.sse.ServerSentEvent import sttp.tapir.* import sttp.tapir.server.netty.sync.serverSentEventsBody import scala.concurrent.duration.* val sseEndpoint = endpoint.get.out(serverSentEventsBody) val sseFlow = Flow .tick(1.second) // emit a new event every second .take(10) .map(_ => s"Event at ${System.currentTimeMillis()}") .map(event => ServerSentEvent(data = Some(event))) val sseServerEndpoint = sseEndpoint.handleSuccess(_ => sseFlow) ``` ``` -------------------------------- ### Armeria Server with Scala Future Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/armeria.md Implement and run a Tapir endpoint using Scala's Future with the Armeria server. This example shows how to define an endpoint, its logic, and mount it on an Armeria server. ```scala import sttp.tapir.* import sttp.tapir.server.armeria.ArmeriaFutureServerInterpreter import scala.concurrent.Future import com.linecorp.armeria.server.Server // JVM entry point that starts the HTTP server - uncommment @main to run /* @main */ def armeriaSerer(): Unit = val tapirEndpoint: PublicEndpoint[(String, Int), Unit, String, Any] = ??? // your definition here def logic(s: String, i: Int): Future[Either[Unit, String]] = ??? // your logic here val tapirService = ArmeriaFutureServerInterpreter().toService(tapirEndpoint.serverLogic((logic _).tupled)) val server = Server .builder() .service(tapirService) // your endpoint is bound to the server .build() server.start().join() ``` -------------------------------- ### Expose http4s Server with Tapir Routes Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/tutorials/07_cats_effect.md Set up and run an http4s server using BlazeServerBuilder, integrating Tapir-interpreted routes. This example extends IOApp for Cats Effect compatibility and binds the server to localhost:8080. ```scala //> using dep com.softwaremill.sttp.tapir::tapir-core:1.13.18 //> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.13.18 //> 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) ``` -------------------------------- ### Tapir OpenAPI Docs Generation and Server Setup Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/tutorials/02_openapi_docs.md This Scala code sets up two tapir endpoints and then generates OpenAPI documentation for them using `SwaggerInterpreter`. It configures a Netty server to serve both the application endpoints and the generated Swagger UI. ```scala //> using dep com.softwaremill.sttp.tapir::tapir-core:1.13.18 //> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.13.18 //> using dep com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.13.18 import sttp.shared.Identity import sttp.tapir.* import sttp.tapir.server.netty.sync.NettySyncServer import sttp.tapir.swagger.bundle.SwaggerInterpreter @main def tapirDocs(): Unit = val e1 = endpoint .get.in("hello" / "world").in(query[String]("name")) .out(stringBody) .handleSuccess(name => s"Hello, $name!") val e2 = endpoint .post.in("double").in(stringBody) .out(stringBody) .errorOut(stringBody) .handle { s => s.toIntOption.fold(Left(s"$s is not a number"))(n => Right((n*2).toString)) } val swaggerEndpoints = SwaggerInterpreter() .fromServerEndpoints[Identity](List(e1, e2), "My App", "1.0") NettySyncServer().port(8080) .addEndpoints(List(e1, e2)) .addEndpoints(swaggerEndpoints) .startAndWait() ``` -------------------------------- ### Test Hello World Endpoint with curl Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/tutorials/01_hello_world.md Demonstrates how to test the running "Hello World" HTTP server using curl, sending a request with a query parameter. ```bash # first console % scala-cli hello.scala Compiling project (Scala 3.4.2, JVM (21)) Compiled project (Scala 3.4.2, JVM (21)) # another console % curl "http://localhost:8080/hello/world?name=Alice" Hello, Alice! ``` -------------------------------- ### Generated JSON Schema Example Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/docs/json-schema.md This is an example of the JSON schema output generated from the provided Tapir schema and serialized with Circe. ```json { "$schema" : "http://json-schema.org/draft-04/schema#", "required" : [ "innerChildField", "childDetails" ], "type" : "object", "properties" : { "innerChildField" : { "$ref" : "#/$defs/Child" }, "childDetails" : { "$ref" : "#/$defs/Child1" } }, "$defs" : { "Child" : { "title" : "Child", "required" : [ "childName" ], "type" : "object", "properties" : { "childName" : { "type" : "string" } } }, "Child1" : { "title" : "my child", "required" : [ "age" ], "type" : "object", "properties" : { "age" : { "type" : "integer", "format" : "int32" }, "height" : { "type" : [ "integer", "null" ], "format" : "int32" } } } } } ``` -------------------------------- ### Verify Shadowed Endpoints Example 2 Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/testing.md Detect shadowed endpoints with named path parameters. This example shows how endpoints with different named path segments can be considered shadowed. ```scala import sttp.tapir.testing.EndpointVerifier val e1 = endpoint.get.in(path[String].name("y_1") / path[String].name("y_2")) val e2 = endpoint.get.in(path[String].name("y_3") / path[String].name("y_4")) val res = EndpointVerifier(List(e1, e2)) ``` ```scala res.toString // res3: String = "Set(GET /{y_3}/{y_4}, is shadowed by: GET /{y_1}/{y_2})" ``` -------------------------------- ### Verify Shadowed Endpoints Example 1 Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/testing.md Detect cases where one endpoint shadows another by overlapping paths. This example demonstrates how overlapping path segments can lead to shadowing. ```scala import sttp.tapir.testing.EndpointVerifier val e1 = endpoint.get.in("x" / paths) val e2 = endpoint.get.in("x" / "y" / "x") val e3 = endpoint.get.in("x") val e4 = endpoint.get.in("y" / "x") val res = EndpointVerifier(List(e1, e2, e3, e4)) ``` ```scala res.toString // res2: String = "Set(GET /x, is shadowed by: GET /x/*, GET /x/y/x, is shadowed by: GET /x/*)" ``` -------------------------------- ### Verify Incorrect Path Consumption Example Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/testing.md Detect endpoints where a wildcard path segment consumes the entire remaining path, potentially omitting subsequent inputs. This example highlights such a case. ```scala import sttp.tapir.testing.EndpointVerifier val e = endpoint.options.in("a" / "b" / "c").securityIn("x" / "y" / paths) val result = EndpointVerifier(List(e)) ``` ```scala result.toString // res4: String = "Set(A wildcard pattern in OPTIONS /x/y/*/a/b/c shadows the rest of the paths at index 2)" ``` -------------------------------- ### Serve Static Files from Local Filesystem Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/endpoint/static.md Use `staticFilesGetServerEndpoint` to expose content from a local directory. Requests to the specified path prefix will be mapped to files within the local system path. ```scala import sttp.tapir.* import sttp.tapir.files.* import sttp.tapir.server.netty.sync.NettySyncServer NettySyncServer() .addEndpoint(staticFilesGetServerEndpoint("site" / "static")("/home/static/data")) .startAndWait() ``` -------------------------------- ### Web Sockets with Netty and Cats Effect Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/netty.md This example demonstrates how to create a web socket endpoint using tapir with the Netty server and Cats Effect. It shows how to define the web socket body, process incoming messages with a `fs2.Pipe`, and handle connection logic. ```APIDOC ## Web Sockets with Netty and Cats Effect This example demonstrates how to create a web socket endpoint using tapir with the Netty server and Cats Effect. It shows how to define the web socket body, process incoming messages with a `fs2.Pipe`, and handle connection logic. ```scala import cats.effect.kernel.Resource import cats.effect.{IO, ResourceApp} import cats.syntax.all.* import fs2.Pipe import sttp.capabilities.fs2.Fs2Streams import sttp.tapir.* import sttp.tapir.server.netty.cats.NettyCatsServer import sttp.ws.WebSocketFrame import scala.concurrent.duration.* object WebSocketsNettyCatsServer extends ResourceApp.Forever { // Web socket endpoint val wsEndpoint = endpoint.get .in("ws") .out( webSocketBody[String, CodecFormat.TextPlain, String, CodecFormat.TextPlain](Fs2Streams[IO]) .concatenateFragmentedFrames(false) // All these options are supported by tapir-netty .ignorePong(true) .autoPongOnPing(true) .decodeCloseRequests(false) .decodeCloseResponses(false) .autoPing(Some((10.seconds, WebSocketFrame.Ping("ping-content".getBytes)))) ) // Your processor transforming a stream of requests into a stream of responses val pipe: Pipe[IO, String, String] = requestStream => requestStream.evalMap(str => IO.pure(str.toUpperCase)) // Alternatively, requests can be ignored and the backend can be turned into a stream emitting frames to the client: // val pipe: Pipe[IO, String, String] = requestStream => someDataEmittingStream.concurrently(requestStream.as(())) val wsServerEndpoint = wsEndpoint.serverLogicSuccess(_ => IO.pure(pipe)) // A regular /GET endpoint val helloWorldEndpoint: PublicEndpoint[String, Unit, String, Any] = endpoint.get.in("hello").in(query[String]("name")).out(stringBody) val helloWorldServerEndpoint = helloWorldEndpoint .serverLogicSuccess(name => IO.pure(s"Hello, $name!")) override def run(args: List[String]) = NettyCatsServer .io() .flatMap { server => Resource .make( server .port(8080) .host("localhost") .addEndpoints(List(wsServerEndpoint, helloWorldServerEndpoint)) .start() )(_.stop()) .as(()) } } ``` ``` -------------------------------- ### Configure OpenTelemetry Metrics Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/observability.md Initialize OpenTelemetry metrics with a MeterProvider and Meter, then create a metrics interceptor to add to your server options. ```scala import sttp.tapir.server.metrics.opentelemetry.OpenTelemetryMetrics import io.opentelemetry.api.metrics.{Meter, MeterProvider} import scala.concurrent.Future val provider: MeterProvider = ??? val meter: Meter = provider.get("instrumentation-name") val metrics = OpenTelemetryMetrics.default[Future](meter) val metricsInterceptor = metrics.metricsInterceptor() // add to your server options ``` -------------------------------- ### Overwriting Response Specification Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/client/sttp.md It's possible to overwrite the response handling description, for example, when testing redirects. ```APIDOC ## Overwriting the response specification The `Request` obtained from the `.toRequest` and `.toSecureRequest` families of methods, after being applied to the input, contains both the request data (URI, headers, body), and a description of how to handle the response - depending on the variant used, decoding the response into one of endpoint’s outputs. If you’d like to skip that step, e.g. when testing redirects, it’s possible to overwrite the response handling description, for example: ```scala import sttp.tapir.* import sttp.tapir.client.sttp4.SttpClientInterpreter import sttp.client4.* SttpClientInterpreter() .toRequest(endpoint.get.in("hello").in(query[String]("name")), Some(uri"http://localhost:8080")) .apply("Ann") .response(asStringAlways) ``` ``` -------------------------------- ### Add jsonschema-circe Dependency Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/docs/json-schema.md Include this dependency to get a codec for serializing `sttp.apispec.Schema` to JSON using Circe. ```scala "com.softwaremill.sttp.apispec" %% "jsonschema-circe" % "..." ``` -------------------------------- ### Generate AsyncAPI Documentation with Servers Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/docs/asyncapi.md Provide a list of servers to the `toAsyncAPI` method to include server information and security requirements in the generated documentation. ```scala import sttp.apispec.asyncapi.Server val docsWithServers: AsyncAPI = AsyncAPIInterpreter().toAsyncAPI( echoWS, "Echo web socket", "1.0", List("production" -> Server("api.example.com", "wss")) ) ``` -------------------------------- ### Scala 3 Main Method vs. Scala 2 App Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/scala_2_3_platforms.md Illustrates the syntax difference for defining a main entry point in Scala 3 using `@main` compared to Scala 2 using `object MyApp extends App`. ```scala // in Scala 3: @main def myExample(): Unit = /* body */ ``` ```scala // in Scala 2: object MyExample extends App { /* body */ } ``` -------------------------------- ### Import Finatra Server Interpreter Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/finatra.md Import the FinatraServerInterpreter to create routes for Finatra servers. ```scala import sttp.tapir.server.finatra.FinatraServerInterpreter ``` -------------------------------- ### Configure OpenTelemetry Tracing Interceptor Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/observability.md Example of adding an OpenTelemetryTracing interceptor to NettySyncServer. The interceptor should be added early to handle requests. ```scala import io.opentelemetry.api.OpenTelemetry import sttp.tapir.server.netty.sync.{NettySyncServer, NettySyncServerOptions} import sttp.tapir.server.tracing.opentelemetry.OpenTelemetryTracing val otel: OpenTelemetry = ??? val serverOptions: NettySyncServerOptions = NettySyncServerOptions.customiseInterceptors .prependInterceptor(OpenTelemetryTracing(otel)) .options NettySyncServer().options(serverOptions).addEndpoint(???).startAndWait() ``` -------------------------------- ### Generate and Expose Documentation with Redoc Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/docs/openapi.md This snippet illustrates how to generate OpenAPI documentation and expose it using the Redoc UI. It requires the `tapir-redoc-bundle` dependency and the use of `RedocInterpreter`. ```APIDOC ## Generate and Expose Documentation with Redoc ### Description This example shows how to generate OpenAPI documentation and serve it using the Redoc UI. Similar to the Swagger integration, it involves adding a specific dependency and using the `RedocInterpreter`. ### Dependency ```scala "com.softwaremill.sttp.tapir" %% "tapir-redoc-bundle" % "1.13.18" ``` ### Usage ```scala import sttp.tapir.redoc.bundle.RedocInterpreter // Assuming myEndpoints is defined as before val myEndpoints: List[AnyEndpoint] = ??? // Interpret endpoints using RedocInterpreter val redocEndpoints = RedocInterpreter().fromEndpoints[Future](myEndpoints, "My App", "1.0") // Integrate with your server interpreter to serve these endpoints. ``` ### Notes - The `RedocInterpreter` generates server endpoints that serve the OpenAPI YAML and the Redoc UI. - These endpoints are then integrated into your server using a server interpreter. ``` -------------------------------- ### Example cURL Request and Response Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/tutorials/03_json.md Demonstrates how to send a POST request with a JSON body to the Tapir endpoint and the expected JSON response. ```bash # first console % scala-cli json.scala # another console % curl -XPOST "http://localhost:8080" -d '{"name": "salad", "servings": 1, "ingredients": ["lettuce", "tomato", "cucumber"]}' {"name":"salad","healthy":true,"calories":42} ``` -------------------------------- ### Example JSON Output Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/tutorials/03_json.md This is the output of the Scala code when run, showing the JSON string representation of a `Meal` object and its derived `Schema`. ```bash % scala-cli json.scala {"name":"salad","servings":1,"ingredients":["lettuce","tomato","cucumber"]} Schema(SProduct(List(SProductField(FieldName(name,name),Schema(SString(),None,false,None,None,None,None,false,false,All(List()),AttributeMap(Map()))), SProductField(FieldName(servings,servings),Schema(SInteger(),None,false,None,Some(int32),None,None,false,false,All(List()),AttributeMap(Map()))), SProductField(FieldName(ingredients,ingredients),Schema(SArray(Schema(SString(),None,false,None,None,None,None,false,false,All(List()),AttributeMap(Map()))),None,true,None,None,None,None,false,false,All(List()),AttributeMap(Map()))))),Some(SName(.Meal,List())),false,None,None,None,None,false,false,All(List()),AttributeMap(Map())) ``` -------------------------------- ### Mock-Server Imports Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/testing.md Import necessary components for mock-server integration. ```scala import sttp.tapir.server.mockserver.* ``` -------------------------------- ### Exclude Akka Stream Dependency Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/server/akkahttp.md Use sbt exclusion to force your own Akka version, for example, 2.5, when including the tapir-akka-http-server dependency. ```scala "com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.13.18" exclude("com.typesafe.akka", "akka-stream_2.12") ``` -------------------------------- ### Create Mock Server Expectation Source: https://github.com/softwaremill/tapir/blob/master/generated-doc/out/testing.md Set up an expectation for the mock server to match incoming requests and define the successful response. ```scala import sttp.client4.* import sttp.client4.httpclient.HttpClientSyncBackend import sttp.client4.wrappers.TryBackend val testingBackend = TryBackend(HttpClientSyncBackend()) val mockServerClient = SttpMockServerClient(baseUri = uri"http://localhost:1080", testingBackend) val in = "request-id-123" -> SampleIn("John", 23) val out = SampleOut("Hello, John!") val expectation = mockServerClient .whenInputMatches(sampleJsonEndpoint)((), in) .thenSuccess(out) .get ```