Skip to content
This repository has been archived by the owner on Sep 14, 2022. It is now read-only.

Handle route delegation properly #132

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
121 changes: 67 additions & 54 deletions play-2.5/swagger-play2/app/play/modules/swagger/SwaggerPlugin.scala
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
/**
* Copyright 2014 Reverb Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
* Copyright 2014 Reverb Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package play.modules.swagger

import java.io.File
import javax.inject.Inject

import io.swagger.config.{FilterFactory, ScannerFactory}
import play.modules.swagger.util.SwaggerContext
import io.swagger.core.filter.SwaggerSpecFilter
import play.api.inject.ApplicationLifecycle
import play.api.{Logger, Application}
import play.api.routing.Router
import scala.concurrent.Future
import scala.collection.JavaConversions._
import play.routes.compiler.{Route => PlayRoute, Include => PlayInclude, RoutesFileParser, StaticPart}
import play.api.{Application, Logger}
import play.modules.swagger.util.SwaggerContext
import play.routes.compiler.{RoutesFileParser, StaticPart, Include => PlayInclude, Route => PlayRoute}

import scala.collection.JavaConversions._
import scala.concurrent.Future
import scala.io.Source

trait SwaggerPlugin
Expand All @@ -54,33 +55,33 @@ class SwaggerPluginImpl @Inject()(lifecycle: ApplicationLifecycle, router: Route

val title = config.getString("swagger.api.info.title") match {
case None => ""
case Some(value)=> value
case Some(value) => value
}

val description = config.getString("swagger.api.info.description") match {
case None => ""
case Some(value)=> value
case Some(value) => value
}

val termsOfServiceUrl = config.getString("swagger.api.info.termsOfServiceUrl") match {
case None => ""
case Some(value)=> value
case Some(value) => value
}

val contact = config.getString("swagger.api.info.contact") match {
case None => ""
case Some(value)=> value
case Some(value) => value
}

val license = config.getString("swagger.api.info.license") match {
case None => ""
case Some(value)=> value
case Some(value) => value
}

val licenseUrl = config.getString("swagger.api.info.licenseUrl") match {
// licenceUrl needs to be a valid URL to validate against schema
case None => "http://licenseUrl"
case Some(value)=> value
case Some(value) => value
}

SwaggerContext.registerClassLoader(app.classloader)
Expand All @@ -106,44 +107,23 @@ class SwaggerPluginImpl @Inject()(lifecycle: ApplicationLifecycle, router: Route
val routes = parseRoutes

def parseRoutes: List[PlayRoute] = {
def playRoutesClassNameToFileName(className: String) = className.replace(".Routes", ".routes")

val routesFile = config.underlying.hasPath("play.http.router") match {
case false => "routes"
case true => config.getString("play.http.router") match {
case None => "routes"
case Some(value)=> playRoutesClassNameToFileName(value)
case Some(value) => SwaggerPluginHelper.playRoutesClassNameToFileName(value)
}
}
//Parses multiple route files recursively
def parseRoutesHelper(routesFile: String, prefix: String): List[PlayRoute] = {
logger.debug(s"Processing route file '$routesFile' with prefix '$prefix'")

val routesContent = Source.fromInputStream(app.classloader.getResourceAsStream(routesFile)).mkString
val parsedRoutes = RoutesFileParser.parseContent(routesContent,new File(routesFile))
val routes = parsedRoutes.right.get.collect {
case (route: PlayRoute) => {
logger.debug(s"Adding route '$route'")
Seq(route.copy(path = route.path.copy(parts = StaticPart(prefix) +: route.path.parts)))
}
case (include: PlayInclude) => {
logger.debug(s"Processing route include $include")
parseRoutesHelper(playRoutesClassNameToFileName(include.router), include.prefix)
}
}.flatten
logger.debug(s"Finished processing route file '$routesFile'")
routes
}
parseRoutesHelper(routesFile, "")

SwaggerPluginHelper.parseRoutes(routesFile, "", logger.debug(_), app.classloader)
}

val routesRules = Map(routes map
{ route =>
{
val routeName = s"${route.call.packageName}.${route.call.controller}$$.${route.call.method}"
(routeName -> route)
}
} : _*)
val routesRules = Map(routes map { route => {
val routeName = s"${route.call.packageName}.${route.call.controller}$$.${route.call.method}"
(routeName -> route)
}
}: _*)

val route = new RouteWrapper(routesRules)
RouteFactory.setRoute(route)
Expand Down Expand Up @@ -174,3 +154,36 @@ class SwaggerPluginImpl @Inject()(lifecycle: ApplicationLifecycle, router: Route
}

}

object SwaggerPluginHelper {
def playRoutesClassNameToFileName(className: String): String = className.replace(".Routes", ".routes")

//Parses multiple route files recursively
def parseRoutes(routesFile: String, prefix: String, debug: String => Unit, classLoader: ClassLoader): List[PlayRoute] = {
debug(s"Processing route file '$routesFile' with prefix '$prefix'")

val routesContent = Source.fromInputStream(classLoader.getResourceAsStream(routesFile)).mkString
val parsedRoutes = RoutesFileParser.parseContent(routesContent, new File(routesFile))
val routes = parsedRoutes.right.get.collect {
case (route: PlayRoute) =>
debug(s"Adding route '$route'")
(prefix, route.path.parts) match {
case ("", _) => Seq(route)
case (_, Seq()) => Seq(route.copy(path = route.path.copy(parts = StaticPart(prefix) +: route.path.parts)))
case (_, Seq(StaticPart(""))) => Seq(route.copy(path = route.path.copy(parts = StaticPart(prefix) +: route.path.parts)))
case (_, Seq(StaticPart("/"))) => Seq(route.copy(path = route.path.copy(parts = StaticPart(prefix) +: route.path.parts)))
case (_, _) => Seq(route.copy(path = route.path.copy(parts = StaticPart(prefix) +: StaticPart("/") +: route.path.parts)))
}
case (include: PlayInclude) =>
debug(s"Processing route include $include")
val newPrefix = if(prefix == "") {
include.prefix
} else {
s"$prefix/${include.prefix}"
}
parseRoutes(playRoutesClassNameToFileName(include.router), newPrefix, debug, classLoader)
}.flatten
debug(s"Finished processing route file '$routesFile'")
routes
}
}
3 changes: 3 additions & 0 deletions play-2.5/swagger-play2/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,6 @@ pomExtra := {
}

lazy val root = (project in file(".")).enablePlugins(PlayScala)

resourceDirectory in Test := baseDirectory.value / "test-resources"
parallelExecution in Test := false
4 changes: 4 additions & 0 deletions play-2.5/swagger-play2/test-resources/delegated.routes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-> /subdelegated subdelegated.Routes

GET /my/action testdata.DelegatedController.list
GET /all testdata.DelegatedController.list2
1 change: 1 addition & 0 deletions play-2.5/swagger-play2/test-resources/delegation
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-> /api delegated.Routes
3 changes: 3 additions & 0 deletions play-2.5/swagger-play2/test-resources/subdelegated.routes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
GET /my/action testdata.DelegatedController.list3
GET /all testdata.DelegatedController.list4
GET / testdata.DelegatedController.list5
66 changes: 35 additions & 31 deletions play-2.5/swagger-play2/test/PlayApiListingCacheSpec.scala
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import java.io.File

import io.swagger.config.ScannerFactory
import io.swagger.models.{ModelImpl, HttpMethod}
import io.swagger.models.parameters.{QueryParameter, BodyParameter, PathParameter}
import io.swagger.models.properties.{RefProperty, ArrayProperty}
import play.modules.swagger._
import org.specs2.mutable._
import io.swagger.models.parameters.{BodyParameter, PathParameter, QueryParameter}
import io.swagger.models.properties.{ArrayProperty, RefProperty}
import io.swagger.models.{HttpMethod, ModelImpl}
import io.swagger.util.Json
import org.specs2.mock.Mockito
import org.specs2.mutable._
import org.specs2.specification.BeforeAfterAll
import play.api.Logger
import io.swagger.util.Json
import play.modules.swagger._
import play.routes.compiler.{Route => PlayRoute}

import scala.collection.JavaConversions._
import scala.collection.JavaConverters._
import play.routes.compiler.{ Route => PlayRoute }

class PlayApiListingCacheSpec extends Specification with Mockito {
class PlayApiListingCacheSpec extends Specification with Mockito with BeforeAfterAll {

override def afterAll(): Unit = {}

// set up mock for Play Router
val routesList = {
play.routes.compiler.RoutesFileParser.parseContent("""
val routesList: List[PlayRoute] = {
play.routes.compiler.RoutesFileParser.parseContent(
"""
POST /api/document/:settlementId/files/:fileId/accept testdata.DocumentController.accept(settlementId:String,fileId:String)
GET /api/search testdata.SettlementsSearcherController.search(personalNumber:String,propertyId:String)
GET /api/pointsofinterest testdata.PointOfInterestController.list(eastingMin:Double,northingMin:Double,eastingMax:Double,northingMax:Double)
Expand All @@ -36,19 +41,17 @@ PUT /api/dog/:id testdata.DogController.add0(id:String)
}
}

val routesRules = Map(routesList map
{ route =>
{
val routeName = s"${route.call.packageName}.${route.call.controller}$$.${route.call.method}"
routeName -> route
}
} : _*)
val routesRules = Map(routesList map { route => {
val routeName = s"${route.call.packageName}.${route.call.controller}$$.${route.call.method}"
routeName -> route
}
}: _*)


val apiVersion = "test1"
val basePath = "/api"

var swaggerConfig = new PlaySwaggerConfig()

val swaggerConfig = new PlaySwaggerConfig()
swaggerConfig setDescription "description"
swaggerConfig setBasePath basePath
swaggerConfig setContact "contact"
Expand All @@ -59,12 +62,13 @@ PUT /api/dog/:id testdata.DogController.add0(id:String)
swaggerConfig setLicense "license"
swaggerConfig setLicenseUrl "http://licenseUrl"

PlayConfigFactory.setConfig(swaggerConfig)
override def beforeAll(): Unit = {
ApiListingCache.cache = None
PlayConfigFactory.setConfig(swaggerConfig)
ScannerFactory.setScanner(new PlayApiScanner())
RouteFactory.setRoute(new RouteWrapper(routesRules))
}

var scanner = new PlayApiScanner()
ScannerFactory.setScanner(scanner)
val route = new RouteWrapper(routesRules)
RouteFactory.setRoute(route)

"ApiListingCache" should {

Expand All @@ -73,7 +77,7 @@ PUT /api/dog/:id testdata.DogController.add0(id:String)
val docRoot = ""
val swagger = ApiListingCache.listing(docRoot, "127.0.0.1")

Logger.debug ("swagger: " + toJsonString(swagger))
Logger.debug("swagger: " + toJsonString(swagger))
swagger must beSome

swagger must beSome
Expand Down Expand Up @@ -157,18 +161,18 @@ PUT /api/dog/:id testdata.DogController.add0(id:String)
val opDogGet = pathDog.getOperationMap.get(HttpMethod.GET)
opDogGet.getOperationId must beEqualTo("listDogs")
opDogGet.getParameters must beEmpty
opDogGet.getConsumes.asScala.toList must beEqualTo(List("application/json","application/xml"))
opDogGet.getConsumes.asScala.toList must beEqualTo(List("application/json", "application/xml"))
opDogGet.getResponses.get("200").getSchema.asInstanceOf[ArrayProperty].getItems.asInstanceOf[RefProperty].getSimpleRef must beEqualTo("Dog")
opDogGet.getProduces.asScala.toList must beEqualTo(List("application/json","application/xml"))
opDogGet.getProduces.asScala.toList must beEqualTo(List("application/json", "application/xml"))

val opDogPut = pathDog.getOperationMap.get(HttpMethod.PUT)
opDogPut.getOperationId must beEqualTo("add1")
opDogPut.getParameters.head.getName must beEqualTo("dog")
opDogPut.getParameters.head.getIn must beEqualTo("body")
opDogPut.getParameters.head.asInstanceOf[BodyParameter].getSchema.getReference must beEqualTo("#/definitions/Dog")
opDogPut.getConsumes.asScala.toList must beEqualTo(List("application/json","application/xml"))
opDogPut.getConsumes.asScala.toList must beEqualTo(List("application/json", "application/xml"))
opDogPut.getResponses.get("200").getSchema.asInstanceOf[RefProperty].getSimpleRef must beEqualTo("ActionAnyContent")
opDogPut.getProduces.asScala.toList must beEqualTo(List("application/json","application/xml"))
opDogPut.getProduces.asScala.toList must beEqualTo(List("application/json", "application/xml"))

val pathDogParam = swagger.get.getPaths.get("/dog/{id}")
pathDogParam.getOperations.size must beEqualTo(1)
Expand All @@ -178,8 +182,8 @@ PUT /api/dog/:id testdata.DogController.add0(id:String)
opDogParamPut.getParameters.head.getName must beEqualTo("id")
opDogParamPut.getParameters.head.getIn must beEqualTo("path")
opDogParamPut.getParameters.head.asInstanceOf[PathParameter].getType must beEqualTo("string")
opDogParamPut.getConsumes.asScala.toList must beEqualTo(List("application/json","application/xml"))
opDogParamPut.getProduces.asScala.toList must beEqualTo(List("application/json","application/xml"))
opDogParamPut.getConsumes.asScala.toList must beEqualTo(List("application/json", "application/xml"))
opDogParamPut.getProduces.asScala.toList must beEqualTo(List("application/json", "application/xml"))
opDogParamPut.getResponses.get("200").getSchema.asInstanceOf[RefProperty].getSimpleRef must beEqualTo("ActionAnyContent")

val catDef = swagger.get.getDefinitions.get("Cat").asInstanceOf[ModelImpl]
Expand Down
Loading