From b9a1319214e8efdfe2af737236e7ce6d45f08fc2 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Mon, 22 Jan 2018 14:28:13 -0800 Subject: Include swagger UI in core These changes centralize the location of the swagger UI web files. The git attributes instruct GitHub to treat the web files as vendored code, as to not be included in the project code statistics. See https://github.com/github/linguist for a more detailed explanation. --- src/main/scala/xyz/driver/core/app/DriverApp.scala | 36 +---------- .../driver/core/rest/SingleRequestHttpClient.scala | 2 +- src/main/scala/xyz/driver/core/rest/Swagger.scala | 74 +++++++++++++++++----- 3 files changed, 60 insertions(+), 52 deletions(-) (limited to 'src/main/scala/xyz/driver/core') diff --git a/src/main/scala/xyz/driver/core/app/DriverApp.scala b/src/main/scala/xyz/driver/core/app/DriverApp.scala index 6ba9949..3bd9c41 100644 --- a/src/main/scala/xyz/driver/core/app/DriverApp.scala +++ b/src/main/scala/xyz/driver/core/app/DriverApp.scala @@ -10,11 +10,9 @@ import akka.http.scaladsl.server.RouteResult._ import akka.http.scaladsl.server._ import akka.http.scaladsl.{Http, HttpExt} import akka.stream.ActorMaterializer -import com.github.swagger.akka.SwaggerHttpService._ import com.typesafe.config.Config import com.typesafe.scalalogging.Logger import io.swagger.models.Scheme -import io.swagger.util.Json import org.slf4j.{LoggerFactory, MDC} import xyz.driver.core import xyz.driver.core.rest._ @@ -26,9 +24,7 @@ import xyz.driver.tracing._ import scala.concurrent.duration._ import scala.concurrent.{Await, ExecutionContext} -import scala.reflect.runtime.universe._ import scala.util.Try -import scala.util.control.NonFatal import scalaz.Scalaz.stringInstance import scalaz.syntax.equal._ @@ -73,7 +69,7 @@ class DriverApp( def appRoute: Route = { val serviceTypes = modules.flatMap(_.routeTypes) - val swaggerService = swaggerOverride(serviceTypes) + val swaggerService = new Swagger(baseUrl, Scheme.forValue(scheme) :: Nil, version, serviceTypes, config, log) val swaggerRoute = swaggerService.routes ~ swaggerService.swaggerUI val versionRt = versionRoute(version, gitHash, time.currentTime()) val basicRoutes = new DriverRoute { @@ -125,36 +121,6 @@ class DriverApp( MDC.put("userAgent", extractHeader(request)("user-agent").getOrElse("unknown")) } - protected def swaggerOverride(apiTypes: Seq[Type]): Swagger = { - new Swagger(baseUrl, Scheme.forValue(scheme), version, actorSystem, apiTypes, config) { - override def generateSwaggerJson: String = { - import io.swagger.models.Swagger - - import scala.collection.JavaConverters._ - - try { - val swagger: Swagger = reader.read(toJavaTypeSet(apiTypes).asJava) - - // Removing trailing spaces - swagger.setPaths( - swagger.getPaths.asScala - .map { - case (key, path) => - key.trim -> path - } - .toMap - .asJava) - - Json.pretty().writeValueAsString(swagger) - } catch { - case NonFatal(t) => - logger.error("Issue with creating swagger.json", t) - throw t - } - } - } - } - protected def versionRoute(version: String, gitHash: String, startupTime: Time): Route = { import spray.json._ import DefaultJsonProtocol._ diff --git a/src/main/scala/xyz/driver/core/rest/SingleRequestHttpClient.scala b/src/main/scala/xyz/driver/core/rest/SingleRequestHttpClient.scala index 4f1f7d0..964a5a2 100644 --- a/src/main/scala/xyz/driver/core/rest/SingleRequestHttpClient.scala +++ b/src/main/scala/xyz/driver/core/rest/SingleRequestHttpClient.scala @@ -24,6 +24,6 @@ class SingleRequestHttpClient(applicationName: Name[App], applicationVersion: St .withConnectionSettings(clientConnectionSettings) def makeRequest(request: HttpRequest): Future[HttpResponse] = { - client.singleRequest(request, settings = connectionPoolSettings)(materializer) + client.singleRequest(request, settings = connectionPoolSettings) } } diff --git a/src/main/scala/xyz/driver/core/rest/Swagger.scala b/src/main/scala/xyz/driver/core/rest/Swagger.scala index de785a7..ab5ad76 100644 --- a/src/main/scala/xyz/driver/core/rest/Swagger.scala +++ b/src/main/scala/xyz/driver/core/rest/Swagger.scala @@ -1,25 +1,69 @@ package xyz.driver.core.rest -import akka.actor.ActorSystem import akka.http.scaladsl.server.Route -import akka.stream._ +import com.github.swagger.akka.SwaggerHttpService import com.github.swagger.akka.model._ -import com.github.swagger.akka.{HasActorSystem, SwaggerHttpService} import com.typesafe.config.Config +import com.typesafe.scalalogging.Logger import io.swagger.models.Scheme +import io.swagger.util.Json -import scala.reflect.runtime.universe._ +import scala.reflect.runtime.universe +import scala.reflect.runtime.universe.Type +import scala.util.control.NonFatal class Swagger( override val host: String, - override val scheme: Scheme, + override val schemes: List[Scheme], version: String, - override val actorSystem: ActorSystem, - override val apiTypes: Seq[Type], - val config: Config) - extends SwaggerHttpService with HasActorSystem { + val apiTypes: Seq[Type], + val config: Config, + val logger: Logger) + extends SwaggerHttpService { - val materializer: ActorMaterializer = ActorMaterializer()(actorSystem) + lazy val mirror = universe.runtimeMirror(getClass.getClassLoader) + + override val apiClasses = apiTypes.map { tpe => + mirror.runtimeClass(tpe.typeSymbol.asClass) + }.toSet + + // Note that the reason for overriding this is a subtle chain of causality: + // + // 1. Some of our endpoints require a single trailing slash and will not + // function if it is omitted + // 2. Swagger omits trailing slashes in its generated api doc + // 3. To work around that, a space is added after the trailing slash in the + // swagger Path annotations + // 4. This space is removed manually in the code below + // + // TODO: Ideally we'd like to drop this custom override and fix the issue in + // 1, by dropping the slash requirement and accepting api endpoints with and + // without trailing slashes. This will require inspecting and potentially + // fixing all service endpoints. + override def generateSwaggerJson: String = { + import io.swagger.models.{Swagger => JSwagger} + + import scala.collection.JavaConverters._ + try { + val swagger: JSwagger = reader.read(apiClasses.asJava) + + // Removing trailing spaces + swagger.setPaths( + swagger.getPaths.asScala + .map { + case (key, path) => + key.trim -> path + } + .toMap + .asJava) + + Json.pretty().writeValueAsString(swagger) + } catch { + case NonFatal(t) => + logger.error("Issue with creating swagger.json", t) + throw t + } + } override val basePath: String = config.getString("swagger.basePath") override val apiDocsPath: String = config.getString("swagger.docsPath") @@ -43,11 +87,9 @@ class Swagger( vendorExtensions = Map.empty[String, AnyRef] ) - def swaggerUI: Route = get { - pathPrefix("") { - pathEndOrSingleSlash { - getFromResource("swagger-ui/index.html") - } + def swaggerUI: Route = + pathEndOrSingleSlash { + getFromResource("swagger-ui/index.html") } ~ getFromResourceDirectory("swagger-ui") - } + } -- cgit v1.2.3 From 197928c370867972e235652b86c6c5d7ea60b071 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Mon, 22 Jan 2018 16:54:48 -0800 Subject: Use dynamic name in generated swagger UI --- src/main/resources/swagger-ui/index.html | 4 +-- src/main/scala/xyz/driver/core/rest/Swagger.scala | 34 ++++++++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) (limited to 'src/main/scala/xyz/driver/core') diff --git a/src/main/resources/swagger-ui/index.html b/src/main/resources/swagger-ui/index.html index 7610421..9691d7d 100644 --- a/src/main/resources/swagger-ui/index.html +++ b/src/main/resources/swagger-ui/index.html @@ -1,7 +1,7 @@ - Users Service + {{title}} @@ -82,7 +82,7 @@