aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/xyz/driver/core
diff options
context:
space:
mode:
authorJakob Odersky <jakob@odersky.com>2018-01-23 11:04:49 -0800
committerGitHub <noreply@github.com>2018-01-23 11:04:49 -0800
commit28dd795938a5ac0ac47f6dd67767fba062f80345 (patch)
treec288d3e37ba12e019e0bb10f57ec829fce7dee1c /src/main/scala/xyz/driver/core
parentf0a16ed58f2808e7dcb17d5a0c42675ae4820a45 (diff)
parent197928c370867972e235652b86c6c5d7ea60b071 (diff)
downloaddriver-core-28dd795938a5ac0ac47f6dd67767fba062f80345.tar.gz
driver-core-28dd795938a5ac0ac47f6dd67767fba062f80345.tar.bz2
driver-core-28dd795938a5ac0ac47f6dd67767fba062f80345.zip
Merge pull request #105 from drivergroup/swaggerv1.6.13
Include swagger UI in core
Diffstat (limited to 'src/main/scala/xyz/driver/core')
-rw-r--r--src/main/scala/xyz/driver/core/app/DriverApp.scala36
-rw-r--r--src/main/scala/xyz/driver/core/rest/SingleRequestHttpClient.scala2
-rw-r--r--src/main/scala/xyz/driver/core/rest/Swagger.scala106
3 files changed, 92 insertions, 52 deletions
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..d110cd3 100644
--- a/src/main/scala/xyz/driver/core/rest/Swagger.scala
+++ b/src/main/scala/xyz/driver/core/rest/Swagger.scala
@@ -1,25 +1,74 @@
package xyz.driver.core.rest
-import akka.actor.ActorSystem
+import akka.http.scaladsl.model.{ContentType, ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Route
-import akka.stream._
+import akka.http.scaladsl.server.directives.FileAndResourceDirectives.ResourceFile
+import akka.stream.ActorAttributes
+import akka.stream.scaladsl.{Framing, StreamConverters}
+import akka.util.ByteString
+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 +92,36 @@ class Swagger(
vendorExtensions = Map.empty[String, AnyRef]
)
- def swaggerUI: Route = get {
- pathPrefix("") {
- pathEndOrSingleSlash {
- getFromResource("swagger-ui/index.html")
- }
- } ~ getFromResourceDirectory("swagger-ui")
+ /** A very simple templating extractor. Gets a resource from the classpath and subsitutes any `{{key}}` with a value. */
+ private def getTemplatedResource(
+ resourceName: String,
+ contentType: ContentType,
+ substitution: (String, String)): Route = get {
+ Option(this.getClass.getClassLoader.getResource(resourceName)) flatMap ResourceFile.apply match {
+ case Some(ResourceFile(url, length, _)) =>
+ extractSettings { settings =>
+ val stream = StreamConverters
+ .fromInputStream(() => url.openStream())
+ .withAttributes(ActorAttributes.dispatcher(settings.fileIODispatcher))
+ .via(Framing.delimiter(ByteString("\n"), 4096, true).map(_.utf8String))
+ .map { line =>
+ line.replaceAll(s"\\{\\{${substitution._1}\\}\\}", substitution._2)
+ }
+ .map(line => ByteString(line))
+ complete(
+ HttpEntity.Default(contentType, length, stream)
+ )
+ }
+ case None => reject
+ }
}
+
+ def swaggerUI: Route =
+ pathEndOrSingleSlash {
+ getTemplatedResource(
+ "swagger-ui/index.html",
+ ContentTypes.`text/html(UTF-8)`,
+ "title" -> config.getString("swagger.apiInfo.title"))
+ } ~ getFromResourceDirectory("swagger-ui")
+
}