aboutsummaryrefslogblamecommitdiff
path: root/src/main/scala/xyz/driver/core/rest/Swagger.scala
blob: a3d942c8f42ff384938ba37921f689c5a835587e (plain) (tree)
1
2
3
4
5
6
7
8
9

                            
                                                                       
                                      



                                                                                  
                                                 
                                      
                                 
                                       
                               
                           
 


                                          
 

                              
                                       
                    



                                
 










































                                                                              






















                                                                         





                                                                                                                          
                                                    







                                                                                   
                                                 
                   
                                           





                         

                          



                                                             
                                              
 
 
package xyz.driver.core.rest

import akka.http.scaladsl.model.{ContentType, ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Route
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.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.Type
import scala.util.control.NonFatal

class Swagger(
    override val host: String,
    override val schemes: List[Scheme],
    version: String,
    val apiTypes: Seq[Type],
    val config: Config,
    val logger: Logger)
    extends SwaggerHttpService {

  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")

  override val info = Info(
    config.getString("swagger.apiInfo.description"),
    version,
    config.getString("swagger.apiInfo.title"),
    config.getString("swagger.apiInfo.termsOfServiceUrl"),
    contact = Some(
      Contact(
        config.getString("swagger.apiInfo.contact.name"),
        config.getString("swagger.apiInfo.contact.url"),
        config.getString("swagger.apiInfo.contact.email")
      )),
    license = Some(
      License(
        config.getString("swagger.apiInfo.license"),
        config.getString("swagger.apiInfo.licenseUrl")
      )),
    vendorExtensions = Map.empty[String, AnyRef]
  )

  /** 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 + "\n"))
          complete(
            HttpEntity(contentType, 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")

}