Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Swagger UI and Redoc for Play #682

Merged
merged 1 commit into from
Jul 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interpreted as:
* [Akka HTTP](https://tapir.softwaremill.com/en/latest/server/akkahttp.html) `Route`s/`Directive`s.
* [Http4s](https://tapir.softwaremill.com/en/latest/server/http4s.html) `HttpRoutes[F]`
* [Finatra](https://tapir.softwaremill.com/en/latest/server/finatra.html) `FinatraRoute`
* [Play](https://tapir.softwaremill.com/en/latest/server/play.html) `Route`
* a client, which is a function from input parameters to output parameters. Currently supported: [sttp](https://tapir.softwaremill.com/en/latest/sttp.html).
* documentation. Currently supported: [OpenAPI](https://tapir.softwaremill.com/en/latest/openapi.html).

Expand Down
20 changes: 20 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ lazy val rootProject = (project in file("."))
swaggerUiHttp4s,
redocHttp4s,
swaggerUiFinatra,
swaggerUiPlay,
redocPlay,
serverTests,
akkaHttpServer,
http4sServer,
Expand Down Expand Up @@ -322,6 +324,24 @@ lazy val swaggerUiFinatra: Project = (project in file("docs/swagger-ui-finatra")
)
.settings(only2_12settings)

lazy val swaggerUiPlay: Project = (project in file("docs/swagger-ui-play"))
.settings(commonSettings)
.settings(
name := "tapir-swagger-ui-play",
libraryDependencies ++= Seq(
"com.typesafe.play" %% "play" % Versions.playServer,
"org.webjars" % "swagger-ui" % Versions.swaggerUi
)
)

lazy val redocPlay: Project = (project in file("docs/redoc-play"))
.enablePlugins(SbtTwirl)
.settings(commonSettings)
.settings(
name := "tapir-redoc-play",
libraryDependencies += "com.typesafe.play" %% "play" % Versions.playServer
)

// server

lazy val serverTests: Project = (project in file("server/tests"))
Expand Down
12 changes: 12 additions & 0 deletions doc/openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,20 @@ akka-http/http4s routes for exposing documentation using [Swagger UI](https://sw
[Redoc](https://github.com/Redocly/redoc):

```scala
// Akka HTTP
"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-akka-http" % "0.16.7"
"com.softwaremill.sttp.tapir" %% "tapir-redoc-akka-http" % "0.16.7"

// Finatra
"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-finatra" % "0.16.7"

// HTTP4S
"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-http4s" % "0.16.7"
"com.softwaremill.sttp.tapir" %% "tapir-redoc-http4s" % "0.16.7"

// Play
"com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-play" % "0.16.7"
"com.softwaremill.sttp.tapir" %% "tapir-redoc-play" % "0.16.7"
```

Note: `tapir-swagger-ui-akka-http` transitively pulls some Akka modules in version 2.6. If you want to force
Expand All @@ -92,6 +102,8 @@ For redoc, use `RedocAkkaHttp`.

For http4s, use the `SwaggerHttp4s` or `RedocHttp4s` classes.

For Play, use `SwaggerPlay` or `RedocPlay` classes.

### Using with sbt-assembly

The `tapir-swagger-ui-*` modules rely on a file in the `META-INF` directory tree, to determine the version of the Swagger UI.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package sttp.tapir.redoc.play

import play.api.mvc.{ActionBuilder, AnyContent, Request}
import play.api.mvc.Results._
import play.api.routing.Router.Routes
import play.api.routing.sird._

/**
* Usage: add `new RedocPlay("My App", yaml).routes` to your Play routes. Docs will be available using the `/docs` path.
* To re-use the ActionBuilder from an existing PlayServerOptions instance, import `sttp.tapir.server.play._`
*
* @param title The title of the HTML page.
* @param yaml The yaml with the OpenAPI documentation.
* @param contextPath The path of the redoc.html page.
* @param yamlName The name of the file, through which the yaml documentation will be served. Defaults to `docs.yaml`.
* @param redocVersion The version of redoc to use. Defaults to `next`. Visit https://github.com/Redocly/redoc for more information.
*/
class RedocPlay(
title: String,
yaml: String,
contextPath: String = "doc",
yamlName: String = "docs.yaml",
redocVersion: String = "next"
)(implicit actionBuilder: ActionBuilder[Request,AnyContent]) {
def routes: Routes = {
case GET(p"/$path") if path == contextPath => actionBuilder {
PermanentRedirect(s"/$contextPath/redoc.html")
}
case GET(p"/$path/$file") if path == contextPath => actionBuilder {
file match {
case "redoc.html" =>
Ok(html.redoc(title, s"/$contextPath/$yamlName", redocVersion))
case `yamlName` =>
Ok(yaml).as("text/yaml")
}

}
}
}
26 changes: 26 additions & 0 deletions docs/redoc-play/src/main/twirl/redoc.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@(title: String, docsPath: String, redocVersion: String)

<!DOCTYPE html>
<html>
<head>
<title>@title</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">

<!--
ReDoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='@docsPath' expand-responses="200,201"></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@@@redocVersion/bundles/redoc.standalone.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package sttp.tapir.swagger.play

import java.util.Properties

import com.typesafe.config.ConfigFactory
import play.api.http.{DefaultFileMimeTypes, FileMimeTypes, FileMimeTypesConfiguration, MimeTypes}
import play.api.mvc.{ActionBuilder, AnyContent, Request}
import play.api.mvc.Results._
import play.api.routing.Router.Routes
import play.api.routing.sird._

import scala.concurrent.ExecutionContext

/**
* Usage: add `new SwaggerPlay(yaml).routes` to your Play routes. Docs will be available using the `/docs` path.
* To re-use the ActionBuilder from an existing PlayServerOptions instance, import `sttp.tapir.server.play._`
*
* @param yaml The yaml with the OpenAPI documentation.
* @param contextPath The context in which the documentation will be served. Defaults to `docs`, so the address
* of the docs will be `/docs`.
* @param yamlName The name of the file, through which the yaml documentation will be served. Defaults to `docs.yaml`.
**/
class SwaggerPlay(
yaml: String,
contextPath: String = "docs",
yamlName: String = "docs.yaml"
)(
implicit ec: ExecutionContext,
actionBuilder: ActionBuilder[Request, AnyContent]
) {
private val redirectPath = s"/$contextPath/index.html?url=/$contextPath/$yamlName"
private val resourcePathPrefix = {
val swaggerVersion: String = {
ConfigFactory.load()
val p = new Properties()
val pomProperties = getClass.getResourceAsStream("/META-INF/maven/org.webjars/swagger-ui/pom.properties")
try p.load(pomProperties)
finally pomProperties.close()
p.getProperty("version")
}
s"META-INF/resources/webjars/swagger-ui/$swaggerVersion"
}

private implicit val swaggerUIFileMimeTypes: FileMimeTypes = new DefaultFileMimeTypes(FileMimeTypesConfiguration(Map(
"html" -> MimeTypes.HTML,
"css" -> MimeTypes.CSS,
"js" -> MimeTypes.JAVASCRIPT,
"png" -> "image/png"
)))

def routes: Routes = {
case GET(p"/$path") if path == contextPath => actionBuilder {
MovedPermanently(redirectPath)
}
case GET(p"/$path/$file") if path == contextPath => actionBuilder {
file match {
case `yamlName` =>
Ok(yaml).as("text/yaml")
case _ =>
Ok.sendResource(s"$resourcePathPrefix/$file")
}
}
}
}
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill" % "1.8.4")
addSbtPlugin("io.spray" % "sbt-boilerplate" % "0.6.1")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.7.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.4.2") // 1.5.x only supports sbt 1.3.0+
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,7 @@ trait TapirPlayServer {
.reduce((a: Routes, b: Routes) => a.orElse(b))
}
}

implicit def actionBuilderFromPlayServerOptions(implicit playServerOptions: PlayServerOptions): ActionBuilder[Request, AnyContent] =
playServerOptions.defaultActionBuilder
}